Skip to content

Commit 1c00525

Browse files
Copilotfelickz
andcommitted
Implement overly permissive role assignment query
Co-authored-by: felickz <1760475+felickz@users.noreply.github.com>
1 parent b62836a commit 1c00525

File tree

7 files changed

+606
-0
lines changed

7 files changed

+606
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/**
2+
* Authorization resource framework for Microsoft.Authorization resources in Bicep.
3+
*
4+
* Provides classes for working with Azure role assignments and other authorization resources.
5+
*
6+
* Classes:
7+
* - RoleAssignmentResource: Represents Microsoft.Authorization/roleAssignments resources.
8+
* - RoleAssignmentProperties: Properties object for role assignments.
9+
*/
10+
11+
private import bicep
12+
private import codeql.bicep.frameworks.Microsoft.General
13+
14+
module Authorization {
15+
private import RoleAssignmentProperties
16+
17+
/**
18+
* Represents a Microsoft.Authorization/roleAssignments resource in a Bicep file.
19+
* See: https://learn.microsoft.com/en-us/azure/templates/microsoft.authorization/roleassignments
20+
*/
21+
class RoleAssignmentResource extends AzureResource {
22+
/**
23+
* Constructs a RoleAssignmentResource for Microsoft.Authorization/roleAssignments resources.
24+
*/
25+
RoleAssignmentResource() {
26+
this.getResourceType().regexpMatch("^Microsoft.Authorization/roleAssignments@.*")
27+
}
28+
29+
/**
30+
* Returns the properties object for the role assignment resource.
31+
*/
32+
Properties getProperties() { result = this.getProperty("properties") }
33+
34+
/**
35+
* Returns the scope property of the role assignment.
36+
* This can be a reference to a subscription, resource group, or specific resource.
37+
*/
38+
Expr getScope() { result = this.getProperty("scope") }
39+
40+
/**
41+
* Returns the name property of the role assignment (typically a GUID).
42+
*/
43+
StringLiteral getName() { result = this.getProperty("name") }
44+
45+
/**
46+
* Gets the role definition ID from the properties.
47+
* This identifies which Azure built-in or custom role is being assigned.
48+
* It may be a direct string literal or extracted from a function call.
49+
*/
50+
string getRoleDefinitionId() {
51+
result = this.getProperties().getRoleDefinitionId()
52+
}
53+
54+
/**
55+
* Gets the principal ID from the properties.
56+
* This identifies the user, group, or service principal receiving the role assignment.
57+
*/
58+
string getPrincipalId() { result = this.getProperties().getPrincipalId() }
59+
60+
/**
61+
* Gets the principal type from the properties.
62+
* This indicates whether the principal is a User, Group, or ServicePrincipal.
63+
*/
64+
string getPrincipalType() { result = this.getProperties().getPrincipalType() }
65+
66+
/**
67+
* Determines if this is a subscription-scoped role assignment.
68+
*/
69+
predicate isSubscriptionScoped() {
70+
exists(CallExpression call |
71+
call = this.getScope() and
72+
call.getName() = "subscription"
73+
)
74+
}
75+
76+
/**
77+
* Determines if this is a resource group-scoped role assignment.
78+
*/
79+
predicate isResourceGroupScoped() {
80+
exists(CallExpression call |
81+
call = this.getScope() and
82+
call.getName() = "resourceGroup"
83+
)
84+
}
85+
86+
/**
87+
* Determines if this role assignment has a broad scope (subscription or resource group).
88+
*/
89+
predicate hasBroadScope() {
90+
this.isSubscriptionScoped() or this.isResourceGroupScoped()
91+
}
92+
93+
/**
94+
* Determines if this role assignment grants a powerful built-in role.
95+
* Checks for common powerful roles like Owner and Contributor.
96+
*/
97+
predicate grantsPrivilegedRole() {
98+
exists(string roleId | roleId = this.getRoleDefinitionId() |
99+
// Owner role
100+
roleId = "8e3af657-a8ff-443c-a75c-2fe8c4bcb635" or
101+
// Contributor role
102+
roleId = "b24988ac-6180-42a0-ab88-20f7382dd24c" or
103+
// User Access Administrator role
104+
roleId = "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9"
105+
)
106+
}
107+
108+
/**
109+
* Determines if this role assignment is overly permissive.
110+
* This checks for privileged roles assigned at broad scopes.
111+
*/
112+
predicate isOverlyPermissive() {
113+
this.hasBroadScope() and this.grantsPrivilegedRole()
114+
}
115+
}
116+
117+
/**
118+
* Module containing property classes for role assignment resources.
119+
*/
120+
private module RoleAssignmentProperties {
121+
/**
122+
* Represents the properties object of a role assignment resource.
123+
*/
124+
class Properties extends ResourceProperties {
125+
private RoleAssignmentResource parent;
126+
127+
/**
128+
* Constructor for the Properties class.
129+
*/
130+
Properties() { this = parent.getProperty("properties") }
131+
132+
/**
133+
* Gets the role definition ID property.
134+
*/
135+
Expr getRoleDefinitionIdProperty() { result = this.getProperty("roleDefinitionId") }
136+
137+
/**
138+
* Returns the role definition ID as a string.
139+
* This handles both direct string literals and subscriptionResourceId function calls.
140+
*/
141+
string getRoleDefinitionId() {
142+
// Direct string literal
143+
result = this.getRoleDefinitionIdProperty().(StringLiteral).getValue()
144+
or
145+
// Extract from subscriptionResourceId function call
146+
exists(CallExpression call |
147+
call = this.getRoleDefinitionIdProperty() and
148+
call.getName() = "subscriptionResourceId" and
149+
result = call.getArgument(1).(StringLiteral).getValue()
150+
)
151+
}
152+
153+
/**
154+
* Determines if the role definition ID property exists.
155+
*/
156+
predicate hasRoleDefinitionId() { exists(this.getRoleDefinitionIdProperty()) }
157+
158+
/**
159+
* Gets the principal ID property.
160+
*/
161+
Expr getPrincipalIdProperty() { result = this.getProperty("principalId") }
162+
163+
/**
164+
* Returns the principal ID as a string.
165+
*/
166+
string getPrincipalId() { result = this.getPrincipalIdProperty().(StringLiteral).getValue() }
167+
168+
/**
169+
* Determines if the principal ID property exists.
170+
*/
171+
predicate hasPrincipalId() { exists(this.getPrincipalIdProperty()) }
172+
173+
/**
174+
* Gets the principal type property.
175+
*/
176+
Expr getPrincipalTypeProperty() { result = this.getProperty("principalType") }
177+
178+
/**
179+
* Returns the principal type as a string.
180+
*/
181+
string getPrincipalType() { result = this.getPrincipalTypeProperty().(StringLiteral).getValue() }
182+
183+
/**
184+
* Determines if the principal type property exists.
185+
*/
186+
predicate hasPrincipalType() { exists(this.getPrincipalTypeProperty()) }
187+
188+
override string toString() { result = "RoleAssignmentProperties" }
189+
}
190+
}
191+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* Security library for detecting overly permissive access control in Bicep templates.
3+
*
4+
* This module provides classes and predicates to identify role assignments that grant
5+
* excessive privileges, particularly broad roles assigned at large scopes.
6+
*/
7+
8+
private import bicep
9+
private import codeql.bicep.dataflow.DataFlow
10+
private import codeql.bicep.frameworks.Microsoft.Authorization
11+
12+
module OverlyPermissiveAccessControl {
13+
/** A data flow source for overly permissive access control vulnerabilities. */
14+
abstract class Source extends DataFlow::Node { }
15+
16+
/** A data flow sink for overly permissive access control vulnerabilities. */
17+
abstract class Sink extends DataFlow::Node { }
18+
19+
/** A sanitizer for overly permissive access control vulnerabilities. */
20+
abstract class Sanitizer extends DataFlow::Node { }
21+
22+
/**
23+
* A role assignment resource that grants privileged roles at broad scopes.
24+
*/
25+
private class OverlyPermissiveRoleAssignment extends Source {
26+
Authorization::RoleAssignmentResource roleAssignment;
27+
28+
OverlyPermissiveRoleAssignment() {
29+
this.asExpr() = roleAssignment.getResourceDeclaration() and
30+
roleAssignment.isOverlyPermissive()
31+
}
32+
33+
/**
34+
* Gets the role assignment resource.
35+
*/
36+
Authorization::RoleAssignmentResource getRoleAssignment() { result = roleAssignment }
37+
38+
/**
39+
* Gets a description of why this role assignment is overly permissive.
40+
*/
41+
string getDescription() {
42+
exists(string role, string scope |
43+
(
44+
roleAssignment.getRoleDefinitionId() = "8e3af657-a8ff-443c-a75c-2fe8c4bcb635" and
45+
role = "Owner"
46+
or
47+
roleAssignment.getRoleDefinitionId() = "b24988ac-6180-42a0-ab88-20f7382dd24c" and
48+
role = "Contributor"
49+
or
50+
roleAssignment.getRoleDefinitionId() = "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9" and
51+
role = "User Access Administrator"
52+
) and
53+
(
54+
roleAssignment.isSubscriptionScoped() and scope = "subscription"
55+
or
56+
roleAssignment.isResourceGroupScoped() and scope = "resource group"
57+
) and
58+
result = role + " role assigned at " + scope + " scope"
59+
)
60+
}
61+
}
62+
63+
/**
64+
* Predicate to identify role assignments with overly broad scope.
65+
*/
66+
predicate hasOverlyBroadScope(Authorization::RoleAssignmentResource roleAssignment) {
67+
roleAssignment.hasBroadScope()
68+
}
69+
70+
/**
71+
* Predicate to identify role assignments with privileged roles.
72+
*/
73+
predicate grantsPrivilegedRole(Authorization::RoleAssignmentResource roleAssignment) {
74+
roleAssignment.grantsPrivilegedRole()
75+
}
76+
77+
/**
78+
* Predicate to identify role assignments that are overly permissive.
79+
*/
80+
predicate isOverlyPermissive(Authorization::RoleAssignmentResource roleAssignment) {
81+
roleAssignment.isOverlyPermissive()
82+
}
83+
}

0 commit comments

Comments
 (0)