Tampering with Conditional Access Policies Using Azure AD Graph API

Summary

Tampering with Conditional Access Policies Using Azure AD Graph API

Summary


Azure
Active
Directory

(Azure
AD)
is
Microsoft’s
cloud-based
identity
and
access
management
service,
and
it
supports
multiple

authentication
methods
.
The
premium
version
of
Azure
AD
also
supports

Conditional
Access
policies

(CAPs)
that
grant
or
block
access
based
on
defined
criteria,
such
as
device
compliance
or
user
location.
Azure
AD
stores
the
settings
for
the
authentication
methods
and
CAPs.
CAPs
can
be
modified
via
the
Azure
AD
portal,
PowerShell,
and
API
calls.

In
May
2022,
Secureworks®
Counter
Threat
Unit™
(CTU)
researchers
investigated
which
APIs
allow
editing
of
CAP
settings
and
identified
three:
the
legacy

Azure
AD
Graph

(also
known
as
AADGraph),

Microsoft
Graph
,
and
an
undocumented
Azure
IAM
API.
AADGraph
was
the
only
API
that
allowed
modification
of
all
CAP
settings,
including
the
metadata.
This
capability
lets
administrators
tamper
with
all
CAP
settings,
including
the
creation
and
modification
timestamps.
Modifications
made
using
AADGraph
are
not
properly
logged,
endangering
integrity
and
non-repudiation
of
Azure
AD
policies.

CTU™
researchers
shared
these
findings
with
Microsoft
on
May
26,
2022.
Microsoft
confirmed
the
findings
a
month
later
but
stated
that
it
is
expected
behavior.
On
May
11,
2023,
Microsoft
notified
CTU
researchers
of
planned
changes
to
improve
audit
logs
and
restrict
CAP
updates
via
AADGraph.

Azure
AD
CAPs

Azure
AD
CAPs
allow
organizations
to
grant
or
block
access
to
services
protected
by
Azure
AD.
They
can
also
be
used
for
session
monitoring
and
limiting
a
session
lifetime.
CAPs
are
enforced
during
the
Azure
AD
authentication
process.
Azure
AD
uses
the
following
common
signals
to
make
a
policy
decision:

  • User
    or
    group
    membership
  • IP
    location
    information
  • Device
  • Application

Only
users
with
specific

roles

can
access
CAPs
in
the
Azure
AD
portal
(see
Table
1).

Access
type
Azure
AD
role
Read Global
Administrator
Global
Reader
Security
Administrator
Conditional
Access
Administrator
Security
Reader
Modify Global
Administrator
Security
Administrator
Conditional
Access
Administrator


Table
1.
Azure
AD
roles
required
to
access
CAPs.

Figure
1
shows
an
example
CAP
that
requires
all
users
to
perform
multi-factor
authentication
(MFA).
The
policy
is
not
enabled
in
this
example;
it
is
set
to

Report-only

mode.
This
mode
allows
organizations
to
assess
the
impact
of
the
CAP
before
enforcing
it.



Figure
1.
Sample
CAP.
(Source:
Secureworks)

The
Azure
AD
portal
displays
the
name,
state,
and
creation
and
modification
timestamps
(see
Figure
2).



Figure
2.
List
of
CAPs.
(Source:
Secureworks)

The
Azure
AD
portal
reflects
changes
whenever
the
CAP
is
modified
(see
Figure
3).



Figure
3.
Modified
CAP.
(Source:
Secureworks)

Azure
AD

audit
logs

captures
CAP
creation
and
modification
events
(see
Figure
4).



Figure
4.
Azure
AD
audit
logs
listing
CAP
creation
events
(highlighted
in
red
(bottom))
and
CAP
modification
events
(highlighted
in
green
(top)).
(Source:
Secureworks)

Both
the
‘Add
conditional
access
policy’
and
‘Update
conditional
access
policy’
events
include
details
of
the
modified
properties
(see
Figure
5).
This
feature
provides
a
full
audit
trail
and
includes
modified
settings.



Figure
5.
Audit
log
details
for
the
‘Update
conditional
access
policy’
event.
(Source:
Secureworks)

Modifying
Conditional
Access
with
API
calls

The
Azure
AD
portal
is
a
graphical
user
interface
(GUI)
that
allows
administrators
to
create
and
maintain
CAPs
via
a
browser.
GUIs
can
perform
ad-hoc
tasks
but
not
automation
and
programmatic
access.
To
address
those
needs,
Microsoft
provides
three
APIs
that
can
interact
with
CAPs:

  • Azure
    AD
    IAM
  • MS
    Graph
  • Azure
    AD
    Graph
    (AADGraph)

Azure
AD
IAM
API

The
Azure
AD
portal
uses
an
undocumented
Azure
AD
IAM
API
to
create,
view,
and
edit
CAPs.
The
API
is
available
at
https
://main
.
iam
.
ad
.
ext
.
azure
.
com/api/Policies/Policies.
Because
the
Azure
AD
portal
uses
Azure
AD
IAM
APIs,
access
requires
the
permissions
listed
in
Table
1.
The
API
returns
a
list
of
CAPs
as
a
JSON
object
(see
Figure
6).



Figure
6.
Azure
AD
IAM
API
response.
(Source:
Secureworks)

When
the
API
opens
a
CAP
for
editing,
it
returns
the
CAP
details
as
a
JSON
object
(see
Figure
7).
This
returned
JSON
object
has
many
fields,
which
correspond
to
the
CAP
settings
available
in
the
Azure
AD
portal.
The
response
also
includes
creation
and
modification
timestamps.



Figure
7.
Response
returned
by
the
Azure
AD
IAM
API
call.
(Source:
Secureworks)

Modifying
a
CAP
sends
a
JSON
object
to
https:
//main
.
iam
.
ad
.
ext
.
azure
.
com/api/Policies/ConvertPolicyMsGraph
as
an
HTTP
POST
request.
Figure
8
shows
a
JSON
object
where
the
CAP
state
was
changed
from
Off
to
Report-only.
Only
the
modified
data
and
not
the
metadata
is
sent
to
Azure
AD.



Figure
8.
Azure
AD
IAM
API
CAP
modification
request.
(Source:
Secureworks)

MS
Graph
API

MS
Graph
API
support
for
conditional
access
is

well-documented
,
Microsoft
also
published

examples

for
creating
and
editing
CAPs.
Table
2
lists
the
required
permissions
to
access
CAPs
via
MS
Graph
API.

Access
type
Permissions
Modify
(all
three
required)
Policy.Read.All
Policy.ReadWrite.ConditionalAccess
Application.Read.All
Read Policy.Read.All


Table
2.
MS
Graph
API
permissions
required
for
CAPs.

Users
or
applications
with
these
permissions
can
list
CAPs
by
calling
the
API
at
https:
//graph
.
Microsoft
.
com/v1.0/identity/conditionalAccess/policies.
The
API
returns
all
CAPs
and
details
as
a
JSON
object
(see
Figure
9).



Figure
9.
MS
Graph
API
response.
(Source:
Secureworks)

Creating
or
modifying
a
CAP
uses
the
same
API
endpoint:

Only
the
modified
data
is
sent
to
Azure
AD.
The
metadata
is
not
included.

Azure
AD
Graph
API
(AADGraph)

Microsoft
has
attempted
to
deprecate
the
AADGraph
API
for
years.
As
of
this
publication,
its

retirement

is
scheduled
to
occur
sometime
after
June
30,
2023.
Microsoft
has
removed
public
AADGraph
API
documentation
to
discourage
its
use.

CAPs
can
be
accessed
using
the
AADGraph
API
at
https:
//graph
.
windows
.
net/<tenant>/policies?api-version=<api
version>
,
where

<tenant>

is
the
Azure
AD
tenant
and

<api
version>

is
the
desired
AADGraph
API
version.
Using
1.6
as
the
API
version
returns
some
Azure
AD
policies
that
the
user
can
access
if
they
have
appropriate
permissions,
but
CAPs
are
not
listed.
However,
using
1.61-internal
as
the
version
returns
all
Azure
AD
policies,
including
CAPs,
regardless
of
the
user’s
permissions.
As
a
result,
any
user
of
the
tenant
can
list
CAPs
and
bypass
the
role
requirements.

The
API
returns
all
policies
as
JSON
objects.
Figure
11
shows
a
CAP
policy
(indicated
by
the
policyType
of
18).



Figure
11.
AADGraph
API
response.
(Source:
Secureworks)

The
CAP
settings
and
metadata
are
stored
in
the
policyDetail
attribute
as
a
JSON
object
(see
Figure
12).
Administrators
with
permissions
to
modify
CAPs
can
edit
this
attribute,
enabling
them
to
tamper
with
the
CAP
conditions
and
metadata.



Figure
12.
CAP
settings
in
policyDetail
attribute.
(Source:
Secureworks)

Updating
an
existing
CAP
with
the
AADGraph
API
involves
an
HTTP
PATCH
request
to
https:
//graph
.
windows
.
net/<tenant>/policies/<objectid>?api-version=1.61-internal,
where

<objectid>

is
the
object
ID
of
the
CAP
to
be
modified.
The
content
of
the
request
is
a
JSON
object
that
only
includes
the
policyDetail
attribute
(see
Figure
13).



Figure
13.
Updating
CAP
using
the
AADGraph
API.
(Source:
Secureworks)

Tampering
with
Conditional
Access
policies

CTU
researchers
used
the

AADInternals

toolkit
to
tamper
with
CAPs.
Administrators
or
threat
actors
can
leverage
the
AADGraph
API
to
make
changes
that
are
not
properly
logged.

  1. We
    retrieved
    the
    current
    policyDetail
    value
    of
    the
    example
    CAP:

    1. Acquired
      an
      access
      token
      for
      an
      administrator
      with
      permissions
      to
      modify
      CAPs
    2. Saved
      the
      example
      CAP
      to
      a
      variable
    3. Extracted
      the
      policyDetail
      value
      and
      copy
      the
      data
      to
      the
      clipboard
      (see
      Figure
      14)



      Figure
      14.
      Getting
      current
      CAP
      policyDetail
      using
      AADInternals.
      (Source:
      Secureworks)

  2. We
    pasted
    the
    policyDetail
    value
    into
    a
    text
    editor
    and
    reformatted
    the
    JSON
    for
    readability
    (see
    Figure
    15).
    We
    then
    emptied
    the
    ModifiedDateTime
    attribute
    (see
    line
    4)
    and
    changed
    the
    State
    attribute
    from
    Reporting
    to
    Disabled.
    The
    modified
    JSON
    was
    flattened
    and
    copied
    to
    the
    clipboard.



    Figure
    15.
    Modified
    CAP
    policyDetail.
    (Source:
    Secureworks)

  3. We
    used
    the
    modified
    policyDetail
    from
    the
    clipboard
    to
    update
    the
    CAP
    (see
    Figure
    16).



    Figure
    16.
    Updating
    the
    CAP
    policyDetail
    attribute
    via
    AADInternals.
    (Source:
    Secureworks)

    The
    Azure
    AD
    portal
    updated
    the
    modifications
    within
    a
    minute
    (see
    Figure
    17).



    Figure
    17.
    Modified
    CAP
    in
    Azure
    AD
    portal.
    (Source:
    Secureworks)

When
CAPs
are
updated
via
the
AADGraph
API,
the
‘Update
conditional
access
policy’
event
is
not
generated
in
the
audit
logs
(see
Figure
18).
As
a
result,
there
is
an
incomplete
audit
trail
on
what
modifications
were
made.



Figure
18.
CAP
modification
via
AADGraph
does
not
create
the
Update
conditional
access
event.
(Source:
Secureworks)

Threat
actors
with
administrator
permissions
can
leverage
this
omission
to
obscure
CAPs.
For
instance,
the
PowerShell
script
in
Figure
19
removes
the
timestamps
and
display
names
of
all
CAPs.



Figure
19.
PowerShell
scipt
to
remove
CAP
display
name
and
timestamps.
(Source:
Secureworks)

After
running
the
script,
CAPs
are
fully
functional.
However,
the
Azure
AD
portal
cannot
open
or
edit
them
(see
Figure
20).



Figure
20.
Azure
AD
portal
after
removing
CAP
display
names
and
timestamps.
(Source:
Secureworks)

Administrators
can
still
delete
CAPs
and
make
duplicates
to
view
existing
CAP
settings.
If
organizations
keep
audit
logs
for
a
longer
period
of
time,
they
may
be
able
to
restore
CAP
names
and
timestamps
based
on
historical
audit
log
data.

Communication
with
Microsoft

CTU
researchers
reported
the
metadata
editing
and
logging
issues
to
the
Microsoft
Security
Response
Center
(MSRC)
on
May
20,
2022.
These
issues
were
reported
as
tampering
and
elevation
of
privilege,
as
administrators
are
also
able
to
modify
the
metadata.
The
MSRC
responded
on
June
26:


We
confirm
the
following
behaviors
when
a
Conditional
Access
Policy
is
modified
via
Azure
AD
Graph
APIs
(or
PowerShell
modules
based
on
AAD
Graph
APIs):


Only
Core
Directory
service
Audit
Log
item
is
present
in
the
Audit
Logs,
while
the
corresponding
Conditional
Access
service
Audit
Log
item
is
missing.


Details
of
the
changed
properties
and
values
are
not
present
in
the
Core
Directory
service
Audit
Log
item.


Modified
Date
information
of
the
edited
policy
object
is
not
updated
on
the
Conditional
Access
Azure
Portal
page.


We
analyzed
the
scenario,
and
established
that:


There
is
no
escalation
of
privileges:
only
users
with
the
required
permissions
are
allowed
to
access
or
modify
policy
objects.


Investigations
of
malicious
Conditional
Access
Policies
are
not
affected
due
to
relevant
information
present
in
the
sign-in
logs.


Date,
Activity,
Target,
and
Actor
information
of
policy
changes
are
present
in
the
Activity
Logs,
allowing
admins
to
audit
who
changed
a
policy
and
when.

On
August
23,
2022,
CTU
researchers
notified
the
MSRC
that
all
users
can
read
conditional
access.
This
issue
was
reported
as
elevation
of
privilege,
as
any
user
can
read
CAPs
without
administrator
permissions.
The
MSRC
responded
on
February
2,
2023:


There
are
several
known
experiences
where
an
authenticated
and
authorized
user
is
able
to
read
specific
data
pertaining
to
an
Azure
AD
configuration
such
as
an
authentication
policy
or
other
similar
configurations.


These
cases
are
by
design:
the
user
is
authorized,
the
data
is
read-only
and
doesn’t
contain
any
specific
user
information.


While
reading
data
such
as
an
authentication
policy
is
not
perceived
as
a
security
breach,
we
do
have
optimizations
in
Azure
AD
to
allow
other
data
or
configurations
to
only
be
read
or
changed
based
on
admin
roles
with
specific
edit
/
create/
delete
rights
for
security
purposes.

On
May
11,
2023,
the
MSRC
informed
the
CTU
research
team
of
planned
changes
to
address
these
issues:


  1. Improve
    audit
    logs
    to
    reflect
    the
    type
    of
    policy
    being
    updated
    when
    CA
    policies
    are
    updated
    through
    AAD
    Graph.

  2. We
    will
    prevent
    admins
    from
    using
    AAD
    Graph
    to
    make
    updates
    to
    CA
    policies.


In
addition
to
these
improvements,
AAD
Graph
is
set
to
be
retired.

Conclusion

Administrators
can
use
the
AADGraph
API
to
change
CAPs.
The
API
does
not
properly
log
changes,
and
the
lack
of
an
audit
trail
breaks
integrity
and
non-repudiation
of
CAPs.
As
a
result,
organizations
cannot
trust
CAP
information
shown
in
the
Azure
AD
portal
or
in
directory
audit
logs.
In
addition,
any
tenant
user
can
view
CAPs
without
administrator
permissions.
This
ability
allows
low-privileged
threat
actors
to
identify
gaps
in
CAPs
or
target
them
for
future
modification.
Third-party
tools
such
as

ROADTools

and

TSxAzureADExport

exploit
this
ability.

CTU
researchers
recommend
that
organizations
store
Azure
AD
audit
logs
in
the

Log
Analytics

workspace
or
in
other
storage
solutions
such
as
Secureworks

Taegis™
XDR
.
Organizations
can
detect
CAP
modifications
via
the
AADGraph
API
by
monitoring
audit
logs
for
an
‘Update
policy’
event
that
does
not
have
a
corresponding
‘Update
conditional
access
policy’
event
within
two
seconds.

Appendix

The
following
script
can
restore
the
names
and
modification
dates
of
CAPs
that
have
been
created
or
modified
using
the
Azure
AD
portal
or
the
MS
Graph
API:

# Read legit CAP events from the audit log
$CAPEvents=Get-AADIntAzureAuditLog -Export                    `
            | Where-Object activityDisplayName -in            `
                "Add conditional access policy",              `
                "Update conditional access policy"            `
            | Select-Object "activityDateTime" -ExpandProperty "targetResources" `
            | Select-Object "id","displayName","activityDateTime"
 
# Loop through the events to get the first (latest) update
$CAPInfos=@{}
foreach($CAPEvent in $CAPEvents)
{
    if(!$CAPInfos.ContainsKey($CAPEvent.id))
    {
        $CAPInfos[$CAPEvent.id] = [pscustomobject]@{
                "displayName"      = $CAPEvent.displayName
                "modifiedDateTime" = $CAPEvent.activityDateTime
            }
    }
}
 
# Read current CAPs
$CAPs = Get-AADIntConditionalAccessPolicies
 
# Loop through CAPs
foreach($CAP in $CAPs)
{
    # Create the return value
    $retVal = [pscustomobject][ordered]@{
        "id"      = $CAP.objectId
        "isEmpty" = [string]::IsNullOrWhiteSpace($CAP.displayName)
        "success" = $null
        "name"    = $CAP.displayName
    }
 
    # Check whether the displayName is empty
    if($retVal.isEmpty)
    {
        # Check whether we found the old information
        if($CAPInfo = $CAPInfos[$retVal.id])
        {
            # Get policyDetails and fix Modified Date
            $policyDetail = $CAP.policyDetail[0] | ConvertFrom-Json 
            try
            {
                $policyDetail.ModifiedDateTime = $CAPInfo.modifiedDateTime
            }
            catch{}
 
            $newPolicyDetail = $policyDetail | ConvertTo-Json -Depth 10 -Compress
 
            # Replace name with the old displayName
            $retVal.name = $CAPInfo.displayName
            try
            {
                Set-AADIntAzureADPolicyDetails  -ObjectId     $retVal.id       `
                                                -PolicyDetail $newPolicyDetail `
                                                -DisplayName  $retVal.name     `
                                                | Out-Null
 
                $retVal.success = $true
            }
            catch
            {
                # Failed
                $retVal.success = $false
                $retVal.name = $CAP.displayName
            }
        }
    }
    # Return
    $retVal
} 

The
following
KQL
query
can
be
used
to
identify
‘Update
policy’
events
that
do
not
have
a
corresponding
‘Update
conditional
access
policy’
event
within
two
seconds:

AuditLogs 
| where OperationName == "Update policy"
| mv-expand TargetResources
| where TargetResources.displayName != "Default Policy"
| mv-expand InitiatedBy
| project PolicyName = TargetResources.displayName, PolicyId = tostring(TargetResources.id), UserPrincipalName = InitiatedBy.user.userPrincipalName, UserId = tostring(InitiatedBy.user.id), OperationName, Time = bin(TimeGenerated, 2s), TimeGenerated, CorrelationId
|join kind=leftanti (AuditLogs
        | where OperationName == "Update conditional access policy"
        | mv-expand TargetResources
        | mv-expand InitiatedBy
        | project PolicyId = tostring(TargetResources.id), UserId = tostring(InitiatedBy.user.id), Time = bin(TimeGenerated, 2s)) on PolicyId,UserId,Time
| order by TimeGenerated

About Author

Subscribe To InfoSec Today News

You have successfully subscribed to the newsletter

There was an error while trying to send your request. Please try again.

World Wide Crypto will use the information you provide on this form to be in touch with you and to provide updates and marketing.